diff --git a/bootstrap.py b/bootstrap.py index 1c5138f..8efeae8 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -58,10 +58,15 @@ import yum # pylint:disable=import-error USE_YUM = True except ImportError: - import dnf # pylint:disable=import-error - USE_YUM = False -import rpm # pylint:disable=import-error - + try: + import dnf # pylint:disable=import-error + USE_YUM = False + except ImportError: + pass +try: + import rpm # pylint:disable=import-error +except ImportError: + pass VERSION = '1.7.5' @@ -74,21 +79,37 @@ def get_os(): """ - Helper function to get the operating system (debian,ubuntu,sles,centos,..). - Reads the content of ID in /etc/os-release. + Helper function to get the operating system (debian,ubuntu,sles,centos,..) and the current release. + Reads the content of ID in /etc/os-release. If the file is not accessable, the platform library is used. + Returns a tuple ((string) os_name, (int) os_version) """ os_name_str = "" - with open("/etc/os-release", "r") as release_file: - for line in release_file: - key, val = line.rstrip().split("=") - if key == "ID": - os_name_str = val - break + os_version = -1 + try: + with open("/etc/os-release", "r") as release_file: + for line in release_file: + try: + key, val = line.rstrip().split("=") + if key == "ID": + os_name_str = val.replace('"', '') + if key == "VERSION_ID": + os_str = val.replace('"', '') + if "." in os_str: + index = os_str.index(".") + os_str = os_str[:index] + os_version = int(os_str) + except ValueError: + pass + except IOError: + os_name_str = platform.dist()[0] + os_version = platform.dist()[1] if os_name_str == "": raise Exception("Cannot read operating system ID from /etc/os-release") + if os_version == -1: + raise Exception("Cannot read operating system VERSION_ID from /etc/os-release") - return os_name_str.lower() + return (os_name_str.lower(), os_version) def get_architecture(): @@ -157,32 +178,36 @@ def color_string(msg, color): return '%s%s%s' % (ERROR_COLORS[color], msg, ERROR_COLORS['ENDC']) -def exec_failok(command): +def exec_failok(command, suppress_output=False): """Helper function to call a command with only warning if failing.""" - return exec_command(command, True) + return exec_command(command, fail_ok=True, suppress_output=suppress_output) -def exec_failexit(command): +def exec_failexit(command, suppress_output=False): """Helper function to call a command with error and exit if failing.""" - return exec_command(command, False) + return exec_command(command, fail_ok=False, suppress_output=suppress_output) -def exec_command(command, failok=False): +def exec_command(command, fail_ok=False, suppress_output=False): """Helper function to call a command and handle errors and output.""" filtered_command = filter_string(command) - print_running(filtered_command) + if not suppress_output: + print_running(filtered_command) retcode, output = getstatusoutput(command) if NEED_STATUS_SHIFT: retcode = os.WEXITSTATUS(retcode) print(output) if retcode != 0: - if failok: - print_warning(filtered_command) + if fail_ok: + if not suppress_output: + print_warning(filtered_command) else: - print_error(filtered_command) + if not suppress_output: + print_error(filtered_command) sys.exit(retcode) else: - print_success(filtered_command) + if not suppress_output: + print_success(filtered_command) return retcode @@ -239,7 +264,7 @@ def exec_service(service, command, failonerror=True): exec_command("/sbin/service %s %s" % (service, command), not failonerror) -class OSHandler: +class OSHandler(object): """ An abstract class that handles the interaction and configuration of the operating system used. It defines general functions and empty function @@ -248,94 +273,157 @@ class OSHandler: def __init__(self, in_options): self._options = in_options + os_facts = get_os() + self._os_name = os_facts[0] + self._os_version = os_facts[1] - @classmethod - def install_prereqs(cls): + ## + # Empty Function Bodies + ## + + def _clean_katello_agent(self): + """Remove old Katello agent (aka Gofer) and certificate RPMs.""" + pass + + def _configure_subscription_manager(self): + """ + Configure subscription-manager. + """ + pass + + def _install_client_ca(self, clean=False, unreg=True): + """ + Retrieve Client CA Certificate RPMs from the Satellite 6 server. + Uses --insecure options to curl(1) if instructed to download via HTTPS + This function is usually called with clean=options.force, which ensures + clean_katello_agent() is called if --force is specified. You can optionally + pass unreg=False to bypass unregistering a system (e.g. when moving between + capsules. + """ + pass + + def _install_prereqs(self): """ Install subscription manager and its prerequisites. If subscription-manager is installed already, check to see if we are migrating. If yes, install subscription-manager-migration. Else if subscription-manager and subscription-manager-migration are available in a configured repo, install them both. Otherwise, exit and inform user that we cannot proceed """ - raise Exception("Not implemented") + pass - @classmethod - def check_package_version(cls, package_name, package_version): # parameters are used by function implementation => pylint: disable=unused-argument + def _pm_clean_cache(self): """ - Verify the version of a given package, using the respective package manager. + Clean the cache of the system specific package manager (yum, zypper, apt). """ - raise Exception("Not implemented") + pass - @classmethod - def configure_subscription_manager(cls): + def _pm_install(self, packages="", params="", fail_on_error=True): """ - Configure subscription-manager. + Use the system specific package manager (yum, zypper, apt) to install packages. """ - raise Exception("Not implemented") + pass - @classmethod - def install_simplejson(cls): + def _pm_install_no_gpg(self, packages="", params="", fail_on_error=True): """ - Install simplejson, using the respective package manager. + Use the system specific package manager (yum, zypper, apt) to install packages. """ - raise Exception("Not implemented") + pass - def check_subman_version(self, required_version): + def _pm_make_cache(self): """ - Verify that the command 'subscription-manager' isn't too old. + Make the system specific package manager (yum, zypper, apt) cache the current repos. """ - status, _ = self.check_package_version('subscription-manager', required_version) + pass - return status + def _pm_remove(self, packages, fail_on_error=True): + """ + Use the system specific package manager (yum, zypper, apt) to remove packages. + """ + pass - @classmethod - def get_api_port(cls): - """Helper function to get the server port from Subscription Manager.""" - configparser = SafeConfigParser() - configparser.read('/etc/rhsm/rhsm.conf') - try: - return configparser.get('server', 'port') - except Exception: # noqa: E722, pylint:disable=broad-except - return "443" + def _pm_update(self, packages="", fail_on_error=True): + """ + Use the system specific package manager (yum, zypper, apt) to update packages. + """ + pass - @classmethod - def is_fips(cls): + def _post_registration(self): """ - Checks to see if the system is FIPS enabled. + Make configurations after the registration finished. """ - try: - fips_file = open("/proc/sys/crypto/fips_enabled", "r") - fips_status = fips_file.read(1) - except IOError: - fips_status = "0" - return fips_status == "1" + pass + + def prepare_migration(self): + """ + Preparation of migration when existing system shall not be removed and no new capsule is used. + """ + pass + + ## + # Implemented Functions + ## @classmethod - def get_rhsm_proxy(cls): + def _check_imports(cls, mandatory_modules): """ - Return the proxy server settings from /etc/rhsm/rhsm.conf as dictionary proxy_config. + Check whether the required imports are satisfied. """ - rhsmconfig = SafeConfigParser() - rhsmconfig.read('/etc/rhsm/rhsm.conf') - proxy_options = [option for option in rhsmconfig.options('server') if option.startswith('proxy')] - proxy_config = {} - for option in proxy_options: - proxy_config[option] = rhsmconfig.get('server', option) - return proxy_config + for module in mandatory_modules: + try: + sys.modules[module] + except KeyError: + if mandatory_modules[module] != "": + print_error("Python module %s is mandatory but cannot be imported. Install %s to proceed." % (module, mandatory_modules[module])) + else: + print_error("Python module %s is mandatory but cannot be imported. Install it and retry.") + sys.exit(1) + + def _check_migration_version(self, required_version): + """ + Verify that the command 'subscription-manager-migration' isn't too old. + """ + status, err = self._check_package_version('subscription-manager-migration', required_version) + + return (status, err) @classmethod - def set_rhsm_proxy(cls, proxy_config): + def _check_package_version(cls, package_name, package_version): """ - Set proxy server settings in /etc/rhsm/rhsm.conf from dictionary saved_proxy_config. + Verify the version of a package. """ - rhsmconfig = SafeConfigParser() - rhsmconfig.read('/etc/rhsm/rhsm.conf') - for option in proxy_config.keys(): - rhsmconfig.set('server', option, proxy_config[option]) - rhsmconfig.write(open('/etc/rhsm/rhsm.conf', 'w')) + required_version = ('0', package_version, '1') + err = "%s not found" % package_name + + transaction_set = rpm.TransactionSet() + db_result = transaction_set.dbMatch('name', package_name) + for package in db_result: + p_name = package['name'].decode('ascii') + p_version = package['version'].decode('ascii') + if rpm.labelCompare(('0', p_version, '1'), required_version) < 0: + err = "%s %s is too old" % (p_name, p_version) + else: + err = None + + return (err is None, err) + + def _check_subman_version(self, required_version): + """ + Verify that the command 'subscription-manager' isn't too old. + """ + status, _ = self._check_package_version('subscription-manager', required_version) + + return status + + def _clean_puppet(self): + """Remove old Puppet Agent and its configuration""" + print_generic("Cleaning old Puppet Agent") + self._pm_remove("puppet-agent", False) + delete_directory("/var/lib/puppet/") + delete_directory("/opt/puppetlabs/puppet/cache") + delete_directory("/etc/puppetlabs/puppet/ssl") @classmethod - def disable_rhn_plugin(cls): + def _disable_rhn_plugin(cls): """ Disable the RHN plugin for Yum """ @@ -349,54 +437,26 @@ def disable_rhn_plugin(cls): if os.path.exists('/etc/sysconfig/rhn/systemid'): os.rename('/etc/sysconfig/rhn/systemid', '/etc/sysconfig/rhn/systemid.bootstrap-bak') + def _enable_repos(self): + """Enable necessary repositories using subscription-manager.""" + repostoenable = " ".join(['--enable=%s' % i for i in self._options.enablerepos.split(',')]) + print_running("Enabling repositories - %s" % self._options.enablerepos) + exec_failok("subscription-manager repos %s" % repostoenable) + @classmethod - def enable_rhsmcertd(cls): + def _enable_rhsmcertd(cls): """ Enable and restart the rhsmcertd service """ enable_service("rhsmcertd") exec_service("rhsmcertd", "restart") - @classmethod - def is_registered(cls): - """ - Check if all required certificates are in place (i.e. a system is - registered to begin with) before we start changing things - """ - return (os.path.exists('/etc/rhsm/ca/katello-server-ca.pem') and - os.path.exists('/etc/pki/consumer/cert.pem')) - - def register_systems(self, org_name, activationkey): - """ - Register the host to Satellite 6's organization using - `subscription-manager` and the given activation key. - Option "--force" is given further. - """ - if 'foreman' in self._options.skip: - org_label = org_name - else: - org_label = return_matching_katello_key('organizations', 'name="%s"' % org_name, 'label', False) - print_generic("Calling subscription-manager") - self._options.smargs += " --serverurl=https://%s:%s/rhsm --baseurl=https://%s/pulp/repos" % (self._options.foreman_fqdn, API_PORT, self._options.foreman_fqdn) - if self._options.force: - self._options.smargs += " --force" - if self._options.release: - self._options.smargs += " --release %s" % self._options.release - if self.check_subman_version(SUBSCRIPTION_MANAGER_SERVER_TIMEOUT_VERSION): - exec_failok("/usr/sbin/subscription-manager config --server.server_timeout=%s" % self._options.timeout) - exec_command("/usr/sbin/subscription-manager register --org '%s' --name '%s' --activationkey '%s' %s" % (org_label, FQDN, activationkey, self._options.smargs), self._options.ignore_registration_failures) - self.enable_rhsmcertd() - - @classmethod - def clean_environment(cls): - """ - Undefine `GEM_PATH`, `LD_LIBRARY_PATH` and `LD_PRELOAD` as many environments - have it defined non-sensibly. - """ - for key in ['GEM_PATH', 'LD_LIBRARY_PATH', 'LD_PRELOAD']: - os.environ.pop(key, None) + def _fully_update_the_box(self): + """Upgrade the host.""" + print_generic("Fully Updating The Box") + self._pm_update() - def generate_katello_facts(self): + def _generate_katello_facts(self): """ Write katello_facts file based on FQDN. Done after installation of katello-ca-consumer RPM in case the script is overriding the @@ -414,46 +474,89 @@ def generate_katello_facts(self): locationfacts.write('{"foreman_location":"%s"}\n' % (self._options.location)) locationfacts.close() - def noop_puppet_signing_run(self): + @classmethod + def _get_rhsm_proxy(cls): """ - Execute Puppet with --noop to generate and sign certs + Return the proxy server settings from /etc/rhsm/rhsm.conf as dictionary proxy_config. """ - print_generic("Running Puppet in noop mode to generate SSL certs") - print_generic("Visit the UI and approve this certificate via Infrastructure->Capsules") - print_generic("if auto-signing is disabled") - exec_failexit("/opt/puppetlabs/puppet/bin/puppet agent --test --noop --tags no_such_tag --waitforcert 10") + rhsmconfig = SafeConfigParser() + rhsmconfig.read('/etc/rhsm/rhsm.conf') + proxy_options = [option for option in rhsmconfig.options('server') if option.startswith('proxy')] + proxy_config = {} + for option in proxy_options: + proxy_config[option] = rhsmconfig.get('server', option) + return proxy_config + + def _install_katello_agent(self): + """Install Katello agent (aka Gofer) and activate /start it.""" + print_generic("Installing the Katello agent") + self._pm_install_no_gpg("katello-agent") + enable_service("goferd") + exec_service("goferd", "restart") + + def _install_katello_host_tools(self): + """Install Katello Host Tools""" + print_generic("Installing the Katello Host Tools") + self._pm_install_no_gpg("katello-host-tools") + + def _install_packages(self): + """Install user-provided packages""" + packages = self._options.install_packages.replace(',', " ") + print_running("Installing the following packages %s" % packages) + self._pm_install_no_gpg(packages, fail_on_error=False) + + def _install_puppet_agent(self): + """Install and configure, then enable and start the Puppet Agent""" + puppet_env = return_puppetenv_for_hg(return_matching_foreman_key('hostgroups', 'title="%s"' % self._options.hostgroup, 'id', False)) + print_generic("Installing the Puppet Agent") + self._pm_install("puppet-agent") + enable_service("puppet") + + puppet_conf_file = '/etc/puppetlabs/puppet/puppet.conf' + main_section = """[main] + vardir = /opt/puppetlabs/puppet/cache + logdir = /var/log/puppetlabs/puppet + rundir = /var/run/puppetlabs + ssldir = /etc/puppetlabs/puppet/ssl + """ + if self._is_fips(): + main_section += "digest_algorithm = sha256" + print_generic("System is in FIPS mode. Setting digest_algorithm to SHA256 in puppet.conf") + puppet_conf = open(puppet_conf_file, 'w') + + # set puppet.conf certname to lowercase FQDN, as capitalized characters would + # get translated anyway generating our certificate + # * https://puppet.com/docs/puppet/3.8/configuration.html#certname + # * https://puppet.com/docs/puppet/4.10/configuration.html#certname + # * https://puppet.com/docs/puppet/5.5/configuration.html#certname + # other links mentioning capitalized characters related issues: + # * https://grokbase.com/t/gg/puppet-users/152s27374y/forcing-a-variable-to-be-lower-case + # * https://groups.google.com/forum/#!topic/puppet-users/vRAu092ppzs + puppet_conf.write(""" +%s +[agent] +pluginsync = true +report = true +ignoreschedules = true +daemon = false +ca_server = %s +certname = %s +environment = %s +server = %s +""" % (main_section, self._options.puppet_ca_server, FQDN.lower(), puppet_env, self._options.puppet_server)) + if self._options.puppet_ca_port: + puppet_conf.write("""ca_port = %s + """ % (options.puppet_ca_port)) + if self._options.puppet_noop: + puppet_conf.write("""noop = true + """) + puppet_conf.close() + self._noop_puppet_signing_run() if 'puppet-enable' not in self._options.skip: enable_service("puppet") exec_service("puppet", "restart") - # curl https://satellite.example.com:9090/ssh/pubkey >> ~/.ssh/authorized_keys - # sort -u ~/.ssh/authorized_keys - def install_ssh_key_from_url(self, remote_url): - """ - Download and install Foreman's SSH public key. - """ - print_generic("Fetching Remote Execution SSH key from %s" % remote_url) - try: - if sys.version_info >= (2, 6): - foreman_ssh_key_req = urllib_urlopen(remote_url, timeout=options.timeout) - else: - foreman_ssh_key_req = urllib_urlopen(remote_url) - foreman_ssh_key = foreman_ssh_key_req.read() - if sys.version_info >= (3, 0): - foreman_ssh_key = foreman_ssh_key.decode(foreman_ssh_key_req.headers.get_content_charset('utf-8')) - except urllib_httperror: - exception = sys.exc_info()[1] - print_generic("The server was unable to fulfill the request. Error: %s - %s" % (exception.code, exception.reason)) - print_generic("Please ensure the Remote Execution feature is configured properly") - print_warning("Installing Foreman SSH key") - return - except urllib_urlerror: - exception = sys.exc_info()[1] - print_generic("Could not reach the server. Error: %s" % exception.reason) - return - self.install_ssh_key_from_string(foreman_ssh_key) - - def install_ssh_key_from_api(self): + def _install_ssh_key_from_api(self): """ Download and install all Foreman's SSH public keys. """ @@ -462,9 +565,9 @@ def install_ssh_key_from_api(self): smart_proxies = get_json(url) for smart_proxy in smart_proxies['results']: if 'remote_execution_pubkey' in smart_proxy: - self.install_ssh_key_from_string(smart_proxy['remote_execution_pubkey']) + self._install_ssh_key_from_string(smart_proxy['remote_execution_pubkey']) - def install_ssh_key_from_string(self, foreman_ssh_key): + def _install_ssh_key_from_string(self, foreman_ssh_key): """ Install Foreman's SSH public key into the foreman user's authorized keys file location, so that remote execution becomes possible. @@ -493,32 +596,443 @@ def install_ssh_key_from_string(self, foreman_ssh_key): print_generic("Foreman's SSH key added to %s" % self._options.remote_exec_authpath) output.close() - def enable_repos(self): - """Enable necessary repositories using subscription-manager.""" - repostoenable = " ".join(['--enable=%s' % i for i in self._options.enablerepos.split(',')]) - print_running("Enabling repositories - %s" % self._options.enablerepos) - exec_failok("subscription-manager repos %s" % repostoenable) - - @classmethod - def check_rpm_installed(cls): - """Check if the machine already has Katello/Spacewalk RPMs installed""" - rpm_sat = ['katello', 'foreman-proxy-content', 'katello-capsule', 'spacewalk-proxy-common', 'spacewalk-backend'] - transaction_set = rpm.TransactionSet() - db_results = transaction_set.dbMatch() - for package in db_results: - package_name = package['name'].decode('ascii') - if package_name in rpm_sat: - print_error("%s RPM found. bootstrap.py should not be used on a Katello/Spacewalk/Satellite host." % (package_name)) - sys.exit(1) - - def prepare_rhel5_migration(self): + # curl https://satellite.example.com:9090/ssh/pubkey >> ~/.ssh/authorized_keys + # sort -u ~/.ssh/authorized_keys + def _install_ssh_key_from_url(self, remote_url): + """ + Download and install Foreman's SSH public key. + """ + print_generic("Fetching Remote Execution SSH key from %s" % remote_url) + try: + if sys.version_info >= (2, 6): + foreman_ssh_key_req = urllib_urlopen(remote_url, timeout=options.timeout) + else: + foreman_ssh_key_req = urllib_urlopen(remote_url) + foreman_ssh_key = foreman_ssh_key_req.read() + if sys.version_info >= (3, 0): + foreman_ssh_key = foreman_ssh_key.decode(foreman_ssh_key_req.headers.get_content_charset('utf-8')) + except urllib_httperror: + exception = sys.exc_info()[1] + print_generic("The server was unable to fulfill the request. Error: %s - %s" % (exception.code, exception.reason)) + print_generic("Please ensure the Remote Execution feature is configured properly") + print_warning("Installing Foreman SSH key") + return + except urllib_urlerror: + exception = sys.exc_info()[1] + print_generic("Could not reach the server. Error: %s" % exception.reason) + return + self._install_ssh_key_from_string(foreman_ssh_key) + + @classmethod + def _is_fips(cls): + """ + Checks to see if the system is FIPS enabled. + """ + try: + fips_file = open("/proc/sys/crypto/fips_enabled", "r") + fips_status = fips_file.read(1) + except IOError: + fips_status = "0" + return fips_status == "1" + + @classmethod + def _is_registered(cls): + """ + Check if all required certificates are in place (i.e. a system is + registered to begin with) before we start changing things + """ + return (os.path.exists('/etc/rhsm/ca/katello-server-ca.pem') and + os.path.exists('/etc/pki/consumer/cert.pem')) + + def _migrate_systems(self, org_name, activationkey): + """ + Call `rhn-migrate-classic-to-rhsm` to migrate the machine from Satellite + 5 to 6 using the organization name/label and the given activation key, and + configure subscription manager with the baseurl of Satellite6's pulp. + This allows the administrator to override the URL provided in the + katello-ca-consumer-latest RPM, which is useful in scenarios where the + Capsules/Servers are load-balanced or using subjectAltName certificates. + If called with "--legacy-purge", uses "legacy-user" and "legacy-password" + to remove the machine. + Option "--force" is always passed so that `rhn-migrate-classic-to-rhsm` + does not fail on channels which cannot be mapped either because they + are cloned channels, custom channels, or do not exist in the destination. + """ + if 'foreman' in self._options.skip: + org_label = org_name + else: + org_label = return_matching_katello_key('organizations', 'name="%s"' % org_name, 'label', False) + print_generic("Calling rhn-migrate-classic-to-rhsm") + self._options.rhsmargs += " --force --destination-url=https://%s:%s/rhsm" % (self._options.foreman_fqdn, API_PORT) + if self._options.legacy_purge: + self._options.rhsmargs += " --legacy-user '%s' --legacy-password '%s'" % (self._options.legacy_login, self._options.legacy_password) + if self._options.removepkgs and self._check_migration_version(SUBSCRIPTION_MANAGER_MIGRATION_REMOVE_PKGS_VERSION)[0]: + self._options.rhsmargs += " --remove-rhn-packages" + else: + self._options.rhsmargs += " --keep" + if self._check_subman_version(SUBSCRIPTION_MANAGER_SERVER_TIMEOUT_VERSION): + exec_failok("/usr/sbin/subscription-manager config --server.server_timeout=%s" % self._options.timeout) + exec_command("/usr/sbin/rhn-migrate-classic-to-rhsm --org %s --activation-key '%s' %s" % (org_label, activationkey, self._options.rhsmargs), self._options.ignore_registration_failures) + exec_command("subscription-manager config --rhsm.baseurl=https://%s/pulp/repos" % self._options.foreman_fqdn, self._options.ignore_registration_failures) + if self._options.release: + exec_failexit("subscription-manager release --set %s" % self._options.release) + self._enable_rhsmcertd() + + # When rhn-migrate-classic-to-rhsm is called with --keep, it will leave the systemid + # file intact, which might confuse the (not yet removed) yum-rhn-plugin. + # Move the file to a backup name & disable the RHN plugin, so the user can still restore it if needed. + self._disable_rhn_plugin() + + def _noop_puppet_signing_run(self): + """ + Execute Puppet with --noop to generate and sign certs + """ + print_generic("Running Puppet in noop mode to generate SSL certs") + print_generic("Visit the UI and approve this certificate via Infrastructure->Capsules") + print_generic("if auto-signing is disabled") + exec_failexit("/opt/puppetlabs/puppet/bin/puppet agent --test --noop --tags no_such_tag --waitforcert 10") + if 'puppet-enable' not in self._options.skip: + enable_service("puppet") + exec_service("puppet", "restart") + + def _register_systems(self, org_name, activationkey): + """ + Register the host to Satellite 6's organization using + `subscription-manager` and the given activation key. + Option "--force" is given further. + """ + if 'foreman' in self._options.skip: + org_label = org_name + else: + org_label = return_matching_katello_key('organizations', 'name="%s"' % org_name, 'label', False) + print_generic("Calling subscription-manager") + self._options.smargs += " --serverurl=https://%s:%s/rhsm --baseurl=https://%s/pulp/repos" % (self._options.foreman_fqdn, API_PORT, self._options.foreman_fqdn) + if self._options.force: + self._options.smargs += " --force" + if self._options.release: + self._options.smargs += " --release %s" % self._options.release + if self._check_subman_version(SUBSCRIPTION_MANAGER_SERVER_TIMEOUT_VERSION): + exec_failok("/usr/sbin/subscription-manager config --server.server_timeout=%s" % self._options.timeout) + exec_command("/usr/sbin/subscription-manager register --org '%s' --name '%s' --activationkey '%s' %s" % (org_label, FQDN, activationkey, self._options.smargs), self._options.ignore_registration_failures) + self._enable_rhsmcertd() + + def _remove_obsolete_packages(self): + """Remove old RHN packages""" + print_generic("Removing old RHN packages") + self._pm_remove(["rhn-setup", "rhn-client-tools", "yum-rhn-plugin", "rhnsd", "rhn-check", "rhnlib", "spacewalk-abrt", "spacewalk-oscap", "osad", "rh-*-rhui-client", "candlepin-cert-consumer-*"], False) + + @classmethod + def _set_rhsm_proxy(cls, proxy_config): + """ + Set proxy server settings in /etc/rhsm/rhsm.conf from dictionary saved_proxy_config. + """ + rhsmconfig = SafeConfigParser() + rhsmconfig.read('/etc/rhsm/rhsm.conf') + for option in proxy_config.keys(): + rhsmconfig.set('server', option, proxy_config[option]) + rhsmconfig.write(open('/etc/rhsm/rhsm.conf', 'w')) + + def _unregister_system(self): + """Unregister the host using `subscription-manager`.""" + print_generic("Cleaning old yum metadata") + self._pm_clean_cache() + print_generic("Unregistering") + exec_failok("/usr/sbin/subscription-manager unregister") + exec_failok("/usr/sbin/subscription-manager clean") + + ## + # Public Functions + ## + + def blank_migration(self): + """ + Blank Migration + > get CA RPM, optionally create host + > register via subscription-manager + """ + print_generic('This system is not registered to RHN. Attempting to register via subscription-manager') + self._install_prereqs() + self._install_client_ca(clean=self._options.force) + self._generate_katello_facts() + if 'foreman' not in self._options.skip: + create_host() + self._configure_subscription_manager() + saved_proxy_config = self._get_rhsm_proxy() + if self._options.preserve_rhsm_proxy: + self._set_rhsm_proxy(saved_proxy_config) + self._register_systems(self._options.org, self._options.activationkey) + if self._options.enablerepos: + self._enable_repos() + + self._post_registration() + + def capsule_migration(self): + """ + Migrate to to other capsule. + > replace CA certificate, reinstall katello-agent, gofer + > optionally update hostgroup and location + > update system definition to point to new capsule for content + > Puppet, OepnSCAP and update Puppet configuration (if applicable) + > MANUAL SIGNING OF CSR OR MANUALLY CREATING AUTO-SIGN RULE STILL REQUIRED! + > API doesn't have a public endpoint for creating auto-sign entries yet! + """ + if not self._is_registered(): + print_error("This system doesn't seem to be registered to a Capsule at this moment.") + sys.exit(1) + + # Make system ready for switch, gather required data + self._install_prereqs() + self._install_client_ca(clean=True, unreg=False) + self._install_katello_host_tools() + if self._options.install_katello_agent: + self._install_katello_agent() + if 'katello-agent' in self._options.skip: + print_warning("Skipping the installation of the Katello Agent is now the default behavior. passing --skip katello-agent is deprecated") + self._enable_rhsmcertd() + + if 'foreman' not in self._options.skip: + current_host_id = return_matching_foreman_key('hosts', 'name="%s"' % self._options.fqdn, 'id', False) + + # Optionally configure new hostgroup, location + if self._options.hostgroup: + print_running("Calling Foreman API to switch hostgroup for %s to %s" % (self._options.fqdn, self._options.hostgroup)) + update_host_config('hostgroup', self._options.hostgroup, current_host_id) + if self._options.location: + print_running("Calling Foreman API to switch location for %s to %s" % (self._options.fqdn, self._options.location)) + update_host_config('location', self._options.location, current_host_id) + + # Configure new proxy_id for Puppet (if not skipped), and OpenSCAP (if available and not skipped) + smart_proxy_id = return_matching_foreman_key('smart_proxies', 'name="%s"' % self._options.foreman_fqdn, 'id', True) + if smart_proxy_id: + capsule_features = get_capsule_features(smart_proxy_id) + if 'puppet' not in self._options.skip: + print_running("Calling Foreman API to update Puppet master and Puppet CA for %s to %s" % (self._options.fqdn, self._options.foreman_fqdn)) + update_host_capsule_mapping("puppet_proxy_id", smart_proxy_id, current_host_id) + update_host_capsule_mapping("puppet_ca_proxy_id", smart_proxy_id, current_host_id) + if 'Openscap' in capsule_features: + print_running("Calling Foreman API to update OpenSCAP proxy for %s to %s" % (self._options.fqdn, self._options.foreman_fqdn)) + update_host_capsule_mapping("openscap_proxy_id", smart_proxy_id, current_host_id) + else: + print_warning("New capsule doesn't have OpenSCAP capability, not switching / configuring openscap_proxy_id") + + print_running("Calling Foreman API to update content source for %s to %s" % (self._options.fqdn, self._options.foreman_fqdn)) + update_host_capsule_mapping("content_source_id", smart_proxy_id, current_host_id) + else: + print_warning("Could not find Smart Proxy '%s'! Will not inform Foreman about the new Puppet/OpenSCAP/content source for %s." % (self._options.foreman_fqdn, self._options.fqdn)) + + if 'puppet' not in self._options.skip: + puppet_conf_path = '/etc/puppetlabs/puppet/puppet.conf' + var_dir = '/opt/puppetlabs/puppet/cache' + ssl_dir = '/etc/puppetlabs/puppet/ssl' + + print_running("Stopping the Puppet agent for configuration update") + exec_service("puppet", "stop") + + # Not using clean_puppet() and install_puppet_agent() here, because + # that would nuke custom /etc/puppet/puppet.conf files, which might + # yield undesirable results. + print_running("Updating Puppet configuration") + exec_failexit("sed -i '/^[[:space:]]*server.*/ s/=.*/= %s/' %s" % (self._options.puppet_server, puppet_conf_path)) + exec_failok("sed -i '/^[[:space:]]*ca_server.*/ s/=.*/= %s/' %s" % (self._options.puppet_ca_server, puppet_conf_path)) # For RHEL5 stock puppet.conf + delete_directory(ssl_dir) + delete_file("%s/client_data/catalog/%s.json" % (var_dir, self._options.fqdn)) + + self._noop_puppet_signing_run() + print_generic("Puppet agent is not running; please start manually if required.") + print_generic("You also need to manually revoke the certificate on the old capsule.") + + @classmethod + def check_package_installed(cls): + """Check if the machine already has Katello/Spacewalk RPMs installed""" + rpm_sat = ['katello', 'foreman-proxy-content', 'katello-capsule', 'spacewalk-proxy-common', 'spacewalk-backend'] + transaction_set = rpm.TransactionSet() + db_results = transaction_set.dbMatch() + for package in db_results: + package_name = package['name'].decode('ascii') + if package_name in rpm_sat: + print_error("%s RPM found. bootstrap.py should not be used on a Katello/Spacewalk/Satellite host." % (package_name)) + sys.exit(1) + + def classic_migration(self): + """ + Host is currently registered to RHN. + > install subscription-manager prerequs + > get CA RPM, optionally create host + > migrate via rhn-classic-migrate-to-rhsm + """ + print_generic('This system is registered to RHN. Attempting to migrate via rhn-classic-migrate-to-rhsm') + self._install_prereqs() + + _, versionerr = self._check_migration_version(SUBSCRIPTION_MANAGER_MIGRATION_MINIMAL_VERSION) + if versionerr: + print_error(versionerr) + sys.exit(1) + + self._install_client_ca(clean=self._options.force) + self._generate_katello_facts() + if 'foreman' not in self._options.skip: + create_host() + self._configure_subscription_manager() + self._migrate_systems(self._options.org, self._options.activationkey) + if self._options.enablerepos: + self._enable_repos() + + @classmethod + def check_rhn_registration(cls): + """Helper function to check if host is registered to legacy RHN.""" + if os.path.exists('/etc/sysconfig/rhn/systemid'): + retcode = getstatusoutput('rhn-channel -l')[0] + if NEED_STATUS_SHIFT: + retcode = os.WEXITSTATUS(retcode) + return retcode == 0 + return False + + @classmethod + def clean_environment(cls): + """ + Undefine `GEM_PATH`, `LD_LIBRARY_PATH` and `LD_PRELOAD` as many environments + have it defined non-sensibly. + """ + for key in ['GEM_PATH', 'LD_LIBRARY_PATH', 'LD_PRELOAD']: + os.environ.pop(key, None) + + def clean_and_update(self): + """ + Install Katello agent, clean & update + > optionally update host + > optionally clean and install Puppet agent + > optionall removy legacy RHN packages + """ + if self._options.install_katello_agent: + self._install_katello_agent() + if 'katello-agent' in options.skip: + print_warning("Skipping the installation of the Katello Agent is now the default behavior. passing --skip katello-agent is deprecated") + if 'katello-host-tools' not in self._options.skip: + self._install_katello_host_tools() + if self._options.update: + self._fully_update_the_box() + + if 'puppet' not in self._options.skip: + if self._options.force: + self._clean_puppet() + self._install_puppet_agent() + + if self._options.install_packages: + self._install_packages() + + if 'remove-obsolete-packages' not in self._options.skip: + self._remove_obsolete_packages() + + if self._options.remote_exec: + if self._options.remote_exec_proxies: + listproxies = self._options.remote_exec_proxies.split(",") + for proxy_fqdn in listproxies: + remote_exec_url = "https://" + str(proxy_fqdn) + ":9090/ssh/pubkey" + self._install_ssh_key_from_url(remote_exec_url) + elif options.remote_exec_url: + self._install_ssh_key_from_url(self._options.remote_exec_url) + elif self._options.remote_exec_apikeys: + self._install_ssh_key_from_api() + else: + remote_exec_url = "https://" + str(self._options.foreman_fqdn) + ":9090/ssh/pubkey" + self._install_ssh_key_from_url(remote_exec_url) + + @classmethod + def get_api_port(cls): + """Helper function to get the server port from Subscription Manager.""" + configparser = SafeConfigParser() + configparser.read('/etc/rhsm/rhsm.conf') + try: + return configparser.get('server', 'port') + except Exception: # noqa: E722, pylint:disable=broad-except + return "443" + + def install_simplejson(self): + """ + Install simplejson, using the respective package manager. + """ + self._pm_install("simplejson") + + def remove_host(self): + """ + Disassociate/delete host, unregister, uninstall katello and optionally + puppet agents. + """ + self._unregister_system() + if 'foreman' not in self._options.skip: + hostid = return_matching_foreman_key('hosts', 'name="%s"' % self._options.fqdn, 'id', True) + if hostid is not None: + disassociate_host(hostid) + delete_host(hostid) + if 'katello-agent' in self._options.skip: + print_warning("Skipping the installation of the Katello Agent is now the default behavior. passing --skip katello-agent is deprecated") + if 'katello-agent' not in self._options.skip or 'katello-host-tools' not in self._options.skip: + self._clean_katello_agent() + if 'puppet' not in self._options.skip: + self._clean_puppet() + + ## + # Properties + ## + + @property + def os_version(self): + """ + Return the version of the OS (RHEL7 => 7). + """ + return self._os_version + + @property + def os_name(self): + """ + Return the name of the OS (Ubuntu => ubuntu) + """ + return self._os_name + + +class OSHandlerRHEL(OSHandler): + """ + OSHandler implementation for RHEL and CentOS systems. + """ + + def __init__(self, in_options): + OSHandler.__init__(self, in_options) + + ## + # Inner Helper Functions + ## + + @classmethod + def _call_yum(cls, command, params="", fail_on_error=True): + """ + Helper function to call a yum command on a list of packages. + pass failonerror = False to make yum commands non-fatal + """ + exec_command("/usr/bin/yum -y %s %s" % (command, params), not fail_on_error) + + def _setup_yum_repo(self, url, gpg_key): + """ + Configures a yum repository at /etc/yum.repos.d/katello-client-bootstrap-deps.repo + """ + submanrepoconfig = SafeConfigParser() + submanrepoconfig.add_section('kt-bootstrap') + submanrepoconfig.set('kt-bootstrap', 'name', 'Subscription-manager dependencies for katello-client-bootstrap') + submanrepoconfig.set('kt-bootstrap', 'baseurl', url) + submanrepoconfig.set('kt-bootstrap', 'enabled', '1') + submanrepoconfig.set('kt-bootstrap', 'gpgcheck', '1') + submanrepoconfig.set('kt-bootstrap', 'gpgkey', gpg_key) + submanrepoconfig.write(open('/etc/yum.repos.d/katello-client-bootstrap-deps.repo', 'w')) + print_generic('Building yum metadata cache. This may take a few minutes') + self._pm_make_cache() + + def _prepare_rhel5_migration(self): """ Execute specific preparations steps for RHEL 5. Older releases of RHEL 5 did not have a version of rhn-classic-migrate-to-rhsm which supported activation keys. This function allows those systems to get a proper product certificate. """ - self.install_prereqs() + self._install_prereqs() # only do the certificate magic if 69.pem is not present # and we have active channels @@ -557,83 +1071,73 @@ class MEOptions: break # cleanup - self.disable_rhn_plugin() - - @classmethod - def check_rhn_registration(cls): - """Helper function to check if host is registered to legacy RHN.""" - if os.path.exists('/etc/sysconfig/rhn/systemid'): - retcode = getstatusoutput('rhn-channel -l')[0] - if NEED_STATUS_SHIFT: - retcode = os.WEXITSTATUS(retcode) - return retcode == 0 - return False - - -class OSHandlerRHEL(OSHandler): - """ - An abstract class that handles the interaction and configuration of the - operating system used. It defines general functions and empty function - bodies which need system-specific implementations. - """ + self._disable_rhn_plugin() - def __init__(self, in_options): - OSHandler.__init__(self, in_options) - - def install_simplejson(self): - self.call_yum("install", "simplejson") + ## + # Parent Functions + ## - @classmethod - def call_yum(cls, command, params="", failonerror=True): - """ - Helper function to call a yum command on a list of packages. - pass failonerror = False to make yum commands non-fatal - """ - exec_command("/usr/bin/yum -y %s %s" % (command, params), not failonerror) - - def check_migration_version(self, required_version): - """ - Verify that the command 'subscription-manager-migration' isn't too old. - """ - status, err = self.check_package_version('subscription-manager-migration', required_version) - - return (status, err) + def _clean_katello_agent(self): + """Remove old Katello agent (aka Gofer) and certificate RPMs.""" + print_generic("Removing old Katello agent and certs") + self._pm_remove("katello-ca-consumer-* katello-agent gofer katello-host-tools katello-host-tools-fact-plugin") + delete_file("/etc/rhsm/ca/katello-server-ca.pem") - def check_package_version(self, package_name, package_version): + def _configure_subscription_manager(self): """ - Verify that the version of a package + Configure subscription-manager plugins in Yum """ - required_version = ('0', package_version, '1') - err = "%s not found" % package_name - - transaction_set = rpm.TransactionSet() - db_result = transaction_set.dbMatch('name', package_name) - for package in db_result: - p_name = package['name'].decode('ascii') - p_version = package['version'].decode('ascii') - if rpm.labelCompare(('0', p_version, '1'), required_version) < 0: - err = "%s %s is too old" % (p_name, p_version) - else: - err = None + productidconfig = SafeConfigParser() + productidconfig.read('/etc/yum/pluginconf.d/product-id.conf') + if productidconfig.get('main', 'enabled') == '0': + print_generic("Product-id yum plugin was disabled. Enabling...") + productidconfig.set('main', 'enabled', '1') + productidconfig.write(open('/etc/yum/pluginconf.d/product-id.conf', 'w')) - return (err is None, err) + submanconfig = SafeConfigParser() + submanconfig.read('/etc/yum/pluginconf.d/subscription-manager.conf') + if submanconfig.get('main', 'enabled') == '0': + print_generic("subscription-manager yum plugin was disabled. Enabling...") + submanconfig.set('main', 'enabled', '1') + submanconfig.write(open('/etc/yum/pluginconf.d/subscription-manager.conf', 'w')) - def setup_yum_repo(self, url, gpg_key): + def _install_client_ca(self, clean=False, unreg=True): """ - Configures a yum repository at /etc/yum.repos.d/katello-client-bootstrap-deps.repo + Retrieve Client CA Certificate RPMs from the Satellite 6 server. + Uses --insecure options to curl(1) if instructed to download via HTTPS + This function is usually called with clean=options.force, which ensures + clean_katello_agent() is called if --force is specified. You can optionally + pass unreg=False to bypass unregistering a system (e.g. when moving between + capsules. """ - submanrepoconfig = SafeConfigParser() - submanrepoconfig.add_section('kt-bootstrap') - submanrepoconfig.set('kt-bootstrap', 'name', 'Subscription-manager dependencies for katello-client-bootstrap') - submanrepoconfig.set('kt-bootstrap', 'baseurl', url) - submanrepoconfig.set('kt-bootstrap', 'enabled', '1') - submanrepoconfig.set('kt-bootstrap', 'gpgcheck', '1') - submanrepoconfig.set('kt-bootstrap', 'gpgkey', gpg_key) - submanrepoconfig.write(open('/etc/yum.repos.d/katello-client-bootstrap-deps.repo', 'w')) - print_generic('Building yum metadata cache. This may take a few minutes') - self.call_yum('makecache') + if clean: + self._clean_katello_agent() + if os.path.exists('/etc/rhsm/ca/katello-server-ca.pem'): + print_generic("A Katello CA certificate is already installed. Assuming system is registered.") + print_generic("If you want to move the system to a different Content Proxy in the same setup, please use --new-capsule.") + print_generic("If you want to remove the old host record and all data associated with it, please use --force.") + print_generic("Exiting.") + sys.exit(1) + if os.path.exists('/etc/pki/consumer/cert.pem') and unreg: + print_generic('System appears to be registered via another entitlement server. Attempting unregister') + self._unregister_system() + if self._options.download_method == "https": + print_generic("Writing custom cURL configuration to allow download via HTTPS without certificate verification") + curl_config_dir = tempfile.mkdtemp() + curl_config = open(os.path.join(curl_config_dir, '.curlrc'), 'w') + curl_config.write("insecure") + curl_config.close() + os.environ["CURL_HOME"] = curl_config_dir + print_generic("Retrieving Client CA Certificate RPMs") + exec_failexit("rpm -Uvh https://%s/pub/katello-ca-consumer-latest.noarch.rpm" % self._options.foreman_fqdn) + print_generic("Deleting cURL configuration") + delete_directory(curl_config_dir) + os.environ.pop("CURL_HOME", None) + else: + print_generic("Retrieving Client CA Certificate RPMs") + exec_failexit("rpm -Uvh http://%s/pub/katello-ca-consumer-latest.noarch.rpm" % self._options.foreman_fqdn) - def install_prereqs(self): + def _install_prereqs(self): """ Install subscription manager and its prerequisites. If subscription-manager is installed already, check to see if we are migrating. If yes, install subscription-manager-migration. @@ -643,7 +1147,7 @@ def install_prereqs(self): print_generic("Checking subscription manager prerequisites") if self._options.deps_repository_url: print_generic("Enabling %s as a repository for dependency RPMs" % self._options.deps_repository_url) - self.setup_yum_repo(self._options.deps_repository_url, self._options.deps_repository_gpg_key) + self._setup_yum_repo(self._options.deps_repository_url, self._options.deps_repository_gpg_key) if USE_YUM: yum_base = yum.YumBase() pkg_list = yum_base.doPackageLists(patterns=['subscription-manager']) @@ -655,136 +1159,148 @@ def install_prereqs(self): pkg_list = dnf_base.sack.query().filter(name='subscription-manager') subman_installed = pkg_list.installed().run() subman_available = pkg_list.available().run() - self.call_yum("remove", "subscription-manager-gnome", False) + self._pm_remove("subscription-manager-gnome", False) if subman_installed: if self.check_rhn_registration() and 'migration' not in self._options.skip: print_generic("installing subscription-manager-migration") - self.call_yum("install", "'subscription-manager-migration-*'", False) + self._pm_install("subscription-manager-migration-*", False) print_generic("subscription-manager is installed already. Attempting update") - self.call_yum("update", "subscription-manager 'subscription-manager-migration-*'", False) + self._pm_update("subscription-manager subscription-manager-migration-*", False) elif subman_available: print_generic("subscription-manager NOT installed. Installing.") - self.call_yum("install", "subscription-manager 'subscription-manager-migration-*'") + self._pm_install("subscription-manager subscription-manager-migration-*") + else: + print_error("Cannot find subscription-manager in any configured repository. Consider using the --deps-repository-url switch to specify a repository with the subscription-manager RPMs") + sys.exit(1) + if 'prereq-update' not in self._options.skip: + self._pm_update("yum openssl python", False) + if self._options.deps_repository_url: + delete_file('/etc/yum.repos.d/katello-client-bootstrap-deps.repo') + + def prepare_migration(self): + """ + Preparation of migration when existing system shall not be removed and no new capsule is used. + """ + if self.os_version == 5: + self._prepare_rhel5_migration() + + def _pm_clean_cache(self): + """ + Clean cache of yum. + """ + self._call_yum("clean", "metadata dbcache") + + def _pm_install(self, packages="", params="", fail_on_error=True): + """ + Use yum to install packages. + """ + if isinstance(packages, list): + pack_str = " ".join(packages) + else: + pack_str = packages + self._call_yum("install %s" % pack_str, fail_on_error) + + def _pm_install_no_gpg(self, packages="", params="", fail_on_error=True): + """ + Use yum to install packages without gpg check. + """ + self._pm_install(packages, ("--nogpgcheck %s" % params), fail_on_error) + + def _pm_make_cache(self): + """ + Create yum cache. + """ + self._call_yum("makecache") + + def _pm_remove(self, packages="", fail_on_error=True): + """ + Use yum to remove packages. + """ + if isinstance(packages, list): + pack_str = " ".join(packages) else: - print_error("Cannot find subscription-manager in any configured repository. Consider using the --deps-repository-url switch to specify a repository with the subscription-manager RPMs") - sys.exit(1) - if 'prereq-update' not in self._options.skip: - self.call_yum("update", "yum openssl python", False) - if self._options.deps_repository_url: - delete_file('/etc/yum.repos.d/katello-client-bootstrap-deps.repo') + pack_str = packages + self._call_yum("remove %s" % pack_str, fail_on_error) - def unregister_system(self): - """Unregister the host using `subscription-manager`.""" - print_generic("Cleaning old yum metadata") - self.call_yum("clean", "metadata dbcache", False) - print_generic("Unregistering") - exec_failok("/usr/sbin/subscription-manager unregister") - exec_failok("/usr/sbin/subscription-manager clean") + def _pm_update(self, packages="", fail_on_error=True): + """ + Use yum to update specific packages. + """ + if isinstance(packages, list): + pack_str = " ".join(packages) + else: + pack_str = packages + self._call_yum("update %s" % pack_str, fail_on_error) - def clean_katello_agent(self): - """Remove old Katello agent (aka Gofer) and certificate RPMs.""" - print_generic("Removing old Katello agent and certs") - self.call_yum("remove", "'katello-ca-consumer-*' katello-agent gofer katello-host-tools katello-host-tools-fact-plugin", False) - delete_file("/etc/rhsm/ca/katello-server-ca.pem") + def _post_registration(self): + """ + Make configurations after the registration finished. + """ + pass - def install_katello_agent(self): - """Install Katello agent (aka Gofer) and activate /start it.""" - print_generic("Installing the Katello agent") - self.call_yum("install", "katello-agent") - enable_service("goferd") - exec_service("goferd", "restart") - def install_katello_host_tools(self): - """Install Katello Host Tools""" - print_generic("Installing the Katello Host Tools") - self.call_yum("install", "katello-host-tools") +class OSHandlerSLES(OSHandler): + """ + OSHandler implementation for SLES and SUSE systems. + """ - def clean_puppet(self): - """Remove old Puppet Agent and its configuration""" - print_generic("Cleaning old Puppet Agent") - self.call_yum("remove", "puppet-agent", False) - delete_directory("/var/lib/puppet/") - delete_directory("/opt/puppetlabs/puppet/cache") - delete_directory("/etc/puppetlabs/puppet/ssl") + def __init__(self, in_options): + OSHandler.__init__(self, in_options) + mandatory_modules = {"rpm": "python-rpm"} + self._check_imports(mandatory_modules) - def remove_obsolete_packages(self): - """Remove old RHN packages""" - print_generic("Removing old RHN packages") - self.call_yum("remove", "rhn-setup rhn-client-tools yum-rhn-plugin rhnsd rhn-check rhnlib spacewalk-abrt spacewalk-oscap osad 'rh-*-rhui-client' 'candlepin-cert-consumer-*'", False) + ## + # Inner Helper Functions + ## - def fully_update_the_box(self): - """Call `yum -y update` to upgrade the host.""" - print_generic("Fully Updating The Box") - self.call_yum("update") + @classmethod + def _call_zypper(cls, command, params="", fail_on_error=True): + """ + Helper function to call a zypper command on a list of packages. + pass fail_on_error=False to make zypper commands non-fatal. + """ + return exec_command("/usr/bin/zypper --quiet --non-interactive %s %s" % (params, command), not fail_on_error) - def install_puppet_agent(self): - """Install and configure, then enable and start the Puppet Agent""" - puppet_env = return_puppetenv_for_hg(return_matching_foreman_key('hostgroups', 'title="%s"' % self._options.hostgroup, 'id', False)) - print_generic("Installing the Puppet Agent") - self.call_yum("install", "puppet-agent") - enable_service("puppet") + def _install_repo(self, repo_url, fail_on_error): + """ + Run zypper addrepo command. Returns the error code of the command as Integer. + """ + print_generic("Add repository for subscription-manager, katello-agent, or-sles-client") + return self._call_zypper("addrepo %s Or_client" % repo_url, "--no-gpg-checks", fail_on_error=fail_on_error) - puppet_conf_file = '/etc/puppetlabs/puppet/puppet.conf' - main_section = """[main] - vardir = /opt/puppetlabs/puppet/cache - logdir = /var/log/puppetlabs/puppet - rundir = /var/run/puppetlabs - ssldir = /etc/puppetlabs/puppet/ssl - """ - if self.is_fips(): - main_section += "digest_algorithm = sha256" - print_generic("System is in FIPS mode. Setting digest_algorithm to SHA256 in puppet.conf") - puppet_conf = open(puppet_conf_file, 'w') + def _setup_repo(self, repo_url): + """ + Add OR_Client repo to install subscription-manager and katello-agent + """ + ret_code = self._install_repo(repo_url, False) + if ret_code == 4: # A repo with the same name exists already + print_generic("Remove existing Or_client repository ") + self._call_zypper("removerepo Or_client") + self._install_repo(repo_url, True) - # set puppet.conf certname to lowercase FQDN, as capitalized characters would - # get translated anyway generating our certificate - # * https://puppet.com/docs/puppet/3.8/configuration.html#certname - # * https://puppet.com/docs/puppet/4.10/configuration.html#certname - # * https://puppet.com/docs/puppet/5.5/configuration.html#certname - # other links mentioning capitalized characters related issues: - # * https://grokbase.com/t/gg/puppet-users/152s27374y/forcing-a-variable-to-be-lower-case - # * https://groups.google.com/forum/#!topic/puppet-users/vRAu092ppzs - puppet_conf.write(""" -%s -[agent] -pluginsync = true -report = true -ignoreschedules = true -daemon = false -ca_server = %s -certname = %s -environment = %s -server = %s -""" % (main_section, self._options.puppet_ca_server, FQDN.lower(), puppet_env, self._options.puppet_server)) - if self._options.puppet_ca_port: - puppet_conf.write("""ca_port = %s - """ % (options.puppet_ca_port)) - if self._options.puppet_noop: - puppet_conf.write("""noop = true - """) - puppet_conf.close() - self.noop_puppet_signing_run() - if 'puppet-enable' not in self._options.skip: - enable_service("puppet") - exec_service("puppet", "restart") + ## + # Parent Functions + ## - def install_packages(self): - """Install user-provided packages""" - packages = self._options.install_packages.replace(',', " ") - print_running("Installing the following packages %s" % packages) - self.call_yum("install", packages, False) + def _clean_katello_agent(self): + """Remove old Katello agent (aka Gofer) and certificate RPMs.""" + print_generic("Removing old Katello agent and certs") + self._pm_remove("katello-agent katello-ca-consumer*") + delete_file("/etc/rhsm/ca/katello-server-ca.pem") + + def _configure_subscription_manager(self): + """ + Configure subscription-manager. + """ + pass - def get_bootstrap_rpm(self, clean=False, unreg=True): + def _install_client_ca(self, clean=False, unreg=True): """ Retrieve Client CA Certificate RPMs from the Satellite 6 server. - Uses --insecure options to curl(1) if instructed to download via HTTPS - This function is usually called with clean=options.force, which ensures - clean_katello_agent() is called if --force is specified. You can optionally - pass unreg=False to bypass unregistering a system (e.g. when moving between - capsules. + Uses --insecure options to curl(1) if instructed to download via HTTPS. """ if clean: - self.clean_katello_agent() + self._clean_katello_agent() if os.path.exists('/etc/rhsm/ca/katello-server-ca.pem'): print_generic("A Katello CA certificate is already installed. Assuming system is registered.") print_generic("If you want to move the system to a different Content Proxy in the same setup, please use --new-capsule.") @@ -793,7 +1309,7 @@ def get_bootstrap_rpm(self, clean=False, unreg=True): sys.exit(1) if os.path.exists('/etc/pki/consumer/cert.pem') and unreg: print_generic('System appears to be registered via another entitlement server. Attempting unregister') - self.unregister_system() + self._unregister_system() if self._options.download_method == "https": print_generic("Writing custom cURL configuration to allow download via HTTPS without certificate verification") curl_config_dir = tempfile.mkdtemp() @@ -802,7 +1318,7 @@ def get_bootstrap_rpm(self, clean=False, unreg=True): curl_config.close() os.environ["CURL_HOME"] = curl_config_dir print_generic("Retrieving Client CA Certificate RPMs") - exec_failexit("rpm -Uvh https://%s/pub/katello-ca-consumer-latest.noarch.rpm" % self._options.foreman_fqdn) + exec_failok("rpm -Uvh https://%s/pub/katello-ca-consumer-latest.noarch.rpm" % self._options.foreman_fqdn) print_generic("Deleting cURL configuration") delete_directory(curl_config_dir) os.environ.pop("CURL_HOME", None) @@ -810,62 +1326,84 @@ def get_bootstrap_rpm(self, clean=False, unreg=True): print_generic("Retrieving Client CA Certificate RPMs") exec_failexit("rpm -Uvh http://%s/pub/katello-ca-consumer-latest.noarch.rpm" % self._options.foreman_fqdn) - def migrate_systems(self, org_name, activationkey): + if self.os_version != 11: + print_generic("Create symlinks for system wide CA usage") + exec_failok("ln -s /etc/rhsm/ca/katello-default-ca.pem /usr/share/pki/trust/anchors/katello-default-ca.pem") + exec_failok("ln -s /etc/rhsm/ca/katello-server-ca.pem /usr/share/pki/trust/anchors/katello-server-ca.pem") + exec_failexit("update-ca-certificates") + + def _install_prereqs(self): """ - Call `rhn-migrate-classic-to-rhsm` to migrate the machine from Satellite - 5 to 6 using the organization name/label and the given activation key, and - configure subscription manager with the baseurl of Satellite6's pulp. - This allows the administrator to override the URL provided in the - katello-ca-consumer-latest RPM, which is useful in scenarios where the - Capsules/Servers are load-balanced or using subjectAltName certificates. - If called with "--legacy-purge", uses "legacy-user" and "legacy-password" - to remove the machine. - Option "--force" is always passed so that `rhn-migrate-classic-to-rhsm` - does not fail on channels which cannot be mapped either because they - are cloned channels, custom channels, or do not exist in the destination. + Install subscription manager and its prerequisites. + If subscription-manager is installed already, check to see if we are migrating. If yes, install subscription-manager-migration. + Else if subscription-manager and subscription-manager-migration are available in a configured repo, install them both. + Otherwise, exit and inform user that we cannot proceed """ - if 'foreman' in self._options.skip: - org_label = org_name - else: - org_label = return_matching_katello_key('organizations', 'name="%s"' % org_name, 'label', False) - print_generic("Calling rhn-migrate-classic-to-rhsm") - self._options.rhsmargs += " --force --destination-url=https://%s:%s/rhsm" % (self._options.foreman_fqdn, API_PORT) - if self._options.legacy_purge: - self._options.rhsmargs += " --legacy-user '%s' --legacy-password '%s'" % (self._options.legacy_login, self._options.legacy_password) - if self._options.removepkgs and self.check_migration_version(SUBSCRIPTION_MANAGER_MIGRATION_REMOVE_PKGS_VERSION)[0]: - self._options.rhsmargs += " --remove-rhn-packages" + if not self._options.deps_repository_url: + print_error("Repository URL not given! Parameter deps-repository-url is mandatory") + sys.exit(1) + self._setup_repo(self._options.deps_repository_url) + self._pm_install_no_gpg("subscription-manager", fail_on_error=True) + if self.os_version == 11: + self._pm_install("libnl3-200 openssl1 curl-openssl1", fail_on_error=False) + self._pm_update("zypper libzypp", fail_on_error=False) + + if 'prereq-update' not in self._options.skip: + self._pm_update("openssl python", False) + + def prepare_migration(self): + """ + Preparation of migration when existing system shall not be removed and no new capsule is used. + """ + pass + + def _pm_clean_cache(self): + """ + Clean cache of zypper. + """ + self._call_zypper("clean") + + def _pm_install(self, packages="", params="", fail_on_error=True): + """ + Use zypper to install packages. + """ + if isinstance(packages, list): + pack_str = " ".join(packages) else: - self._options.rhsmargs += " --keep" - if self.check_subman_version(SUBSCRIPTION_MANAGER_SERVER_TIMEOUT_VERSION): - exec_failok("/usr/sbin/subscription-manager config --server.server_timeout=%s" % self._options.timeout) - exec_command("/usr/sbin/rhn-migrate-classic-to-rhsm --org %s --activation-key '%s' %s" % (org_label, activationkey, self._options.rhsmargs), self._options.ignore_registration_failures) - exec_command("subscription-manager config --rhsm.baseurl=https://%s/pulp/repos" % self._options.foreman_fqdn, self._options.ignore_registration_failures) - if self._options.release: - exec_failexit("subscription-manager release --set %s" % self._options.release) - self.enable_rhsmcertd() + pack_str = packages + self._call_zypper("install %s" % pack_str, params, fail_on_error) - # When rhn-migrate-classic-to-rhsm is called with --keep, it will leave the systemid - # file intact, which might confuse the (not yet removed) yum-rhn-plugin. - # Move the file to a backup name & disable the RHN plugin, so the user can still restore it if needed. - self.disable_rhn_plugin() + def _pm_install_no_gpg(self, packages="", params="", fail_on_error=True): + """ + Use zypper to install packages. + """ + self._pm_install(packages, ("--no-gpg-checks %s" % params), fail_on_error) - def configure_subscription_manager(self): + def _pm_make_cache(self): """ - Configure subscription-manager plugins in Yum + Create zypper cache. """ - productidconfig = SafeConfigParser() - productidconfig.read('/etc/yum/pluginconf.d/product-id.conf') - if productidconfig.get('main', 'enabled') == '0': - print_generic("Product-id yum plugin was disabled. Enabling...") - productidconfig.set('main', 'enabled', '1') - productidconfig.write(open('/etc/yum/pluginconf.d/product-id.conf', 'w')) + pass - submanconfig = SafeConfigParser() - submanconfig.read('/etc/yum/pluginconf.d/subscription-manager.conf') - if submanconfig.get('main', 'enabled') == '0': - print_generic("subscription-manager yum plugin was disabled. Enabling...") - submanconfig.set('main', 'enabled', '1') - submanconfig.write(open('/etc/yum/pluginconf.d/subscription-manager.conf', 'w')) + def _pm_remove(self, packages="", fail_on_error=True): + """ + Use zypper to remove packages. + """ + if isinstance(packages, list): + pack_str = " ".join(packages) + else: + pack_str = packages + self._call_zypper("remove %s" % pack_str, "", fail_on_error) + + def _pm_update(self, packages="", fail_on_error=True): + """ + Use yum to update specific packages. + """ + if isinstance(packages, list): + pack_str = " ".join(packages) + else: + pack_str = packages + self._call_zypper("update %s" % pack_str, "", fail_on_error) class BetterHTTPErrorProcessor(urllib_basehandler): @@ -1149,6 +1687,14 @@ def disassociate_host(host_id): print("Foreman Bootstrap Script") print("This script is designed to register new systems or to migrate an existing system to a Foreman server with Katello") + print("") + + print("Prerequisites for Suse Linux Enterprise Server host:") + print("SLES11 SP4 -> Pool Repository, Update Repository") + print("SLES12 SP5 -> Pool Repository") + print("SLES15 -> Pool Repository, Module-Basesystem Repository") + print("SLES15 SP1/2 -> Pool Repository, Module-Basesystem Repository, Module-Python2 Repository") + print("") # > Register our better HTTP processor as default opener for URLs. opener = urllib_build_opener(BetterHTTPErrorProcessor) @@ -1178,17 +1724,10 @@ def disassociate_host(host_id): API_PORT = "443" ARCHITECTURE = get_architecture() try: - # pylint:disable=deprecated-method - RELEASE = platform.linux_distribution()[1] - except AttributeError: - # pylint:disable=deprecated-method - RELEASE = platform.dist()[1] - IS_EL5 = int(RELEASE[0]) == 5 - IS_EL8 = int(RELEASE[0]) == 8 - if not IS_EL5: - DEFAULT_DOWNLOAD_METHOD = 'https' - else: - DEFAULT_DOWNLOAD_METHOD = 'http' + (OS_NAME, RELEASE) = get_os() + except IOError as e: + print_error("Error while reading operating system: %s" % e) + sys.exit(1) SKIP_STEPS = ['foreman', 'puppet', 'migration', 'prereq-update', 'katello-agent', 'remove-obsolete-packages', 'puppet-enable', 'katello-host-tools'] @@ -1226,7 +1765,7 @@ def disassociate_host(host_id): parser.add_option("--remove", dest="remove", action="store_true", help="Instead of registering the machine to Foreman remove it") parser.add_option("-r", "--release", dest="release", help="Specify release version") parser.add_option("-R", "--remove-obsolete-packages", dest="removepkgs", action="store_true", help="Remove old Red Hat Network and RHUI Packages (default)", default=True) - parser.add_option("--download-method", dest="download_method", default=DEFAULT_DOWNLOAD_METHOD, help="Method to download katello-ca-consumer package (e.g. http or https)", metavar="DOWNLOADMETHOD", choices=['http', 'https']) + parser.add_option("--download-method", dest="download_method", default="https", help="Method to download katello-ca-consumer package (e.g. http or https)", metavar="DOWNLOADMETHOD", choices=['http', 'https']) parser.add_option("--no-remove-obsolete-packages", dest="removepkgs", action="store_false", help="Don't remove old Red Hat Network and RHUI Packages") parser.add_option("--unmanaged", dest="unmanaged", action="store_true", help="Add the server as unmanaged. Useful to skip provisioning dependencies.") parser.add_option("--rex", dest="remote_exec", action="store_true", help="Install Foreman's SSH key for remote execution.", default=False) @@ -1380,32 +1919,24 @@ def disassociate_host(host_id): print_error("This script requires root-level access") sys.exit(1) - # Determine OS - try: - os_name = get_os() - except Exception as e: - print_error("Error while reading operating system: %s" % e) - sys.exit(1) - # Create OSHandler instance depending on OS - if "suse" in os_name or "sles" in os_name: - print_error("SUSE and SLES support has not been implemented yet") - sys.exit(1) - # Implement OSHandlerSLES - # os_handler = OSHandlerSLES(options) - elif "debian" in os_name or "ubuntu" in os_name: + if "suse" in OS_NAME or "sles" in OS_NAME: + os_handler = OSHandlerSLES(options) + elif "debian" in OS_NAME or "ubuntu" in OS_NAME: print_error("Debian and Ubuntu support has not been implemented yet") sys.exit(1) # Implement OSHandlerDebian # os_handler = OSHandlerDebian(options) - elif "centos" in os_name or "rhel" in os_name or "ol" in os_name: + elif "centos" in OS_NAME or "rhel" in OS_NAME or "ol" in OS_NAME: os_handler = OSHandlerRHEL(options) else: - print_error("Detected OS %s is not supported" % os_name) + print_error("Detected OS %s is not supported" % OS_NAME) sys.exit(1) + IS_EL8 = os_handler.os_version == 8 + # > Check if Katello/Spacewalk/Satellite are installed already - os_handler.check_rpm_installed() + os_handler.check_package_installed() # > Try to import json or simplejson. # do it at this point in the code to have our custom print and exec @@ -1428,51 +1959,20 @@ def disassociate_host(host_id): os_handler.clean_environment() # > IF RHEL 5, not removing, and not moving to new capsule prepare the migration. - if not options.remove and IS_EL5 and not options.new_capsule: + if not options.remove and not options.new_capsule: if options.legacy_purge: print_warning("Purging the system from the Legacy environment is not supported on EL5.") - os_handler.prepare_rhel5_migration() + os_handler.prepare_migration() - if options.preserve_rhsm_proxy: - saved_proxy_config = os_handler.get_rhsm_proxy() + API_PORT = os_handler.get_api_port() if options.remove: - # > IF remove, disassociate/delete host, unregister, - # > uninstall katello and optionally puppet agents - API_PORT = os_handler.get_api_port() - os_handler.unregister_system() - if 'foreman' not in options.skip: - hostid = return_matching_foreman_key('hosts', 'name="%s"' % FQDN, 'id', True) - if hostid is not None: - disassociate_host(hostid) - delete_host(hostid) - if 'katello-agent' in options.skip: - print_warning("Skipping the installation of the Katello Agent is now the default behavior. passing --skip katello-agent is deprecated") - if 'katello-agent' not in options.skip or 'katello-host-tools' not in options.skip: - os_handler.clean_katello_agent() - if 'puppet' not in options.skip: - os_handler.clean_puppet() + os_handler.remove_host() elif os_handler.check_rhn_registration() and 'migration' not in options.skip and not IS_EL8: # > ELIF registered to RHN, install subscription-manager prerequs # > get CA RPM, optionally create host, # > migrate via rhn-classic-migrate-to-rhsm - print_generic('This system is registered to RHN. Attempting to migrate via rhn-classic-migrate-to-rhsm') - os_handler.install_prereqs() - - _, versionerr = os_handler.check_migration_version(SUBSCRIPTION_MANAGER_MIGRATION_MINIMAL_VERSION) - if versionerr: - print_error(versionerr) - sys.exit(1) - - os_handler.get_bootstrap_rpm(clean=options.force) - os_handler.generate_katello_facts() - API_PORT = os_handler.get_api_port() - if 'foreman' not in options.skip: - create_host() - os_handler.configure_subscription_manager() - os_handler.migrate_systems(options.org, options.activationkey) - if options.enablerepos: - os_handler.enable_repos() + os_handler.classic_migration() elif options.new_capsule: # > ELIF new_capsule and foreman_fqdn set, will migrate to other capsule # @@ -1482,89 +1982,11 @@ def disassociate_host(host_id): # > Puppet, OpenSCAP and update Puppet configuration (if applicable) # > MANUAL SIGNING OF CSR OR MANUALLY CREATING AUTO-SIGN RULE STILL REQUIRED! # > API doesn't have a public endpoint for creating auto-sign entries yet! - if not os_handler.is_registered(): - print_error("This system doesn't seem to be registered to a Capsule at this moment.") - sys.exit(1) - - # Make system ready for switch, gather required data - os_handler.install_prereqs() - os_handler.get_bootstrap_rpm(clean=True, unreg=False) - os_handler.install_katello_host_tools() - if options.install_katello_agent: - os_handler.install_katello_agent() - if 'katello-agent' in options.skip: - print_warning("Skipping the installation of the Katello Agent is now the default behavior. passing --skip katello-agent is deprecated") - os_handler.enable_rhsmcertd() - - API_PORT = os_handler.get_api_port() - if 'foreman' not in options.skip: - current_host_id = return_matching_foreman_key('hosts', 'name="%s"' % FQDN, 'id', False) - - # Optionally configure new hostgroup, location - if options.hostgroup: - print_running("Calling Foreman API to switch hostgroup for %s to %s" % (FQDN, options.hostgroup)) - update_host_config('hostgroup', options.hostgroup, current_host_id) - if options.location: - print_running("Calling Foreman API to switch location for %s to %s" % (FQDN, options.location)) - update_host_config('location', options.location, current_host_id) - - # Configure new proxy_id for Puppet (if not skipped), and OpenSCAP (if available and not skipped) - smart_proxy_id = return_matching_foreman_key('smart_proxies', 'name="%s"' % options.foreman_fqdn, 'id', True) - if smart_proxy_id: - capsule_features = get_capsule_features(smart_proxy_id) - if 'puppet' not in options.skip: - print_running("Calling Foreman API to update Puppet master and Puppet CA for %s to %s" % (FQDN, options.foreman_fqdn)) - update_host_capsule_mapping("puppet_proxy_id", smart_proxy_id, current_host_id) - update_host_capsule_mapping("puppet_ca_proxy_id", smart_proxy_id, current_host_id) - if 'Openscap' in capsule_features: - print_running("Calling Foreman API to update OpenSCAP proxy for %s to %s" % (FQDN, options.foreman_fqdn)) - update_host_capsule_mapping("openscap_proxy_id", smart_proxy_id, current_host_id) - else: - print_warning("New capsule doesn't have OpenSCAP capability, not switching / configuring openscap_proxy_id") - - print_running("Calling Foreman API to update content source for %s to %s" % (FQDN, options.foreman_fqdn)) - update_host_capsule_mapping("content_source_id", smart_proxy_id, current_host_id) - else: - print_warning("Could not find Smart Proxy '%s'! Will not inform Foreman about the new Puppet/OpenSCAP/content source for %s." % (options.foreman_fqdn, FQDN)) - - if 'puppet' not in options.skip: - puppet_conf_path = '/etc/puppetlabs/puppet/puppet.conf' - var_dir = '/opt/puppetlabs/puppet/cache' - ssl_dir = '/etc/puppetlabs/puppet/ssl' - - print_running("Stopping the Puppet agent for configuration update") - exec_service("puppet", "stop") - - # Not using clean_puppet() and install_puppet_agent() here, because - # that would nuke custom /etc/puppet/puppet.conf files, which might - # yield undesirable results. - print_running("Updating Puppet configuration") - exec_failexit("sed -i '/^[[:space:]]*server.*/ s/=.*/= %s/' %s" % (options.puppet_server, puppet_conf_path)) - exec_failok("sed -i '/^[[:space:]]*ca_server.*/ s/=.*/= %s/' %s" % (options.puppet_ca_server, puppet_conf_path)) # For RHEL5 stock puppet.conf - delete_directory(ssl_dir) - delete_file("%s/client_data/catalog/%s.json" % (var_dir, FQDN)) - - os_handler.noop_puppet_signing_run() - print_generic("Puppet agent is not running; please start manually if required.") - print_generic("You also need to manually revoke the certificate on the old capsule.") - + os_handler.capsule_migration() else: # > ELSE get CA RPM, optionally create host, # > register via subscription-manager - print_generic('This system is not registered to RHN. Attempting to register via subscription-manager') - os_handler.install_prereqs() - os_handler.get_bootstrap_rpm(clean=options.force) - os_handler.generate_katello_facts() - API_PORT = os_handler.get_api_port() - if 'foreman' not in options.skip: - create_host() - os_handler.configure_subscription_manager() - if options.preserve_rhsm_proxy: - os_handler.set_rhsm_proxy(saved_proxy_config) - os_handler.register_systems(options.org, options.activationkey) - if options.enablerepos: - os_handler.enable_repos() - + os_handler.blank_migration() if options.location and 'foreman' in options.skip: delete_file('/etc/rhsm/facts/location.facts') @@ -1572,36 +1994,4 @@ def disassociate_host(host_id): # > IF not removing, install Katello agent, optionally update host, # > optionally clean and install Puppet agent # > optionally remove legacy RHN packages - if options.install_katello_agent: - os_handler.install_katello_agent() - if 'katello-agent' in options.skip: - print_warning("Skipping the installation of the Katello Agent is now the default behavior. passing --skip katello-agent is deprecated") - if 'katello-host-tools' not in options.skip: - os_handler.install_katello_host_tools() - if options.update: - os_handler.fully_update_the_box() - - if 'puppet' not in options.skip: - if options.force: - os_handler.clean_puppet() - os_handler.install_puppet_agent() - - if options.install_packages: - os_handler.install_packages() - - if 'remove-obsolete-packages' not in options.skip: - os_handler.remove_obsolete_packages() - - if options.remote_exec: - if options.remote_exec_proxies: - listproxies = options.remote_exec_proxies.split(",") - for proxy_fqdn in listproxies: - remote_exec_url = "https://" + str(proxy_fqdn) + ":9090/ssh/pubkey" - os_handler.install_ssh_key_from_url(remote_exec_url) - elif options.remote_exec_url: - os_handler.install_ssh_key_from_url(options.remote_exec_url) - elif options.remote_exec_apikeys: - os_handler.install_ssh_key_from_api() - else: - remote_exec_url = "https://" + str(options.foreman_fqdn) + ":9090/ssh/pubkey" - os_handler.install_ssh_key_from_url(remote_exec_url) + os_handler.clean_and_update() diff --git a/pylintrc b/pylintrc index 503fe75..d51025c 100644 --- a/pylintrc +++ b/pylintrc @@ -137,7 +137,7 @@ single-line-if-stmt=no no-space-check=trailing-comma,dict-separator # Maximum number of lines in a module -max-module-lines=2000 +max-module-lines=2500 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). @@ -364,7 +364,7 @@ max-attributes=7 min-public-methods=2 # Maximum number of public methods for a class (see R0904). -max-public-methods=30 +max-public-methods=40 # Maximum number of boolean expressions in a if statement max-bool-expr=5