diff --git a/.gitignore b/.gitignore index d303e70..b2c83b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bootstrap.pyc dist/ MANIFEST +__pycache__/ diff --git a/Makefile b/Makefile index defa66f..cb0b4d9 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ DOCKER=docker -TEST_TARGETS=centos\:5 centos\:6 centos\:7 +TEST_TARGETS=centos\:6 centos\:7 TEST3_TARGETS=fedora\:29 TEST_PLATFORM_TARGETS=ubi8 USE_SELINUX=$(shell test -d /sys/fs/selinux && echo ":Z") diff --git a/bootstrap.py b/bootstrap.py old mode 100755 new mode 100644 index 0e27e49..346c43b --- a/bootstrap.py +++ b/bootstrap.py @@ -35,6 +35,7 @@ urllib_install_opener = urllib.request.install_opener import base64 import sys +import subprocess try: from commands import getstatusoutput NEED_STATUS_SHIFT = True @@ -58,10 +59,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' @@ -72,6 +78,102 @@ OWNER_ONLY_FILE = 384 # octal: 600 +def find_root_cert(path): + """ + Determine the Root certificate among others (intermediate + and server certificates)in a given file. + Returns the certificate as string. + Raises: IOError, Exception + """ + delim_begin = "-----BEGIN CERTIFICATE-----\n" + delim_end = "-----END CERTIFICATE-----\n" + in_cert = False + certs = [] + index = -1 + with open(path, "r") as cert_file: + for line in cert_file: + if line == delim_begin and in_cert is False: + in_cert = True + index += 1 + certs.append(line) + elif line == delim_end and in_cert is True: + in_cert = False + certs[index] += line + elif in_cert is True: + certs[index] += line + + cert_len = len(certs) + + # Determine root certificate + root_cert = None + if cert_len <= 0: + raise Exception("Cannot find any certificate in %s" % path) + if cert_len == 1: + root_cert = certs[0] + else: + # Determine root certificate within multiple certs (same issuer and subject) + for cert in certs: + with subprocess.Popen( + ["openssl x509 -issuer_hash -subject_hash -noout"], + stdin=subprocess.PIPE, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + shell=True + ) as popen: + if not isinstance(cert, bytes): + output, err = popen.communicate(bytes(cert, "utf-8")) + else: + output, err = popen.communicate(cert) + + if popen.returncode != 0: + raise Exception("The following error occured during the determination of the certificate issuer and hash:\nError code: %i\n\nError message:\n%s\n\nCertificate:\n%s" % (popen.returncode, err, cert)) + + iss, sub = output.split() + if iss == sub: + root_cert = cert + break + + if root_cert is None: + raise Exception("Cannot determine root certificate (same issuer and subject) in %s" % path) + + return root_cert + + +def get_os(): + """ + 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 = "" + 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(), os_version) + + def get_architecture(): """ Helper function to get the architecture x86_64 vs. x86. @@ -138,33 +240,38 @@ 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 not suppress_output: + 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) - return retcode + if not suppress_output: + print_success(filtered_command) + return (retcode, output) def delete_file(filename): @@ -197,494 +304,1453 @@ def delete_directory(directoryname): sys.exit(1) -def call_yum(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(required_version): - """ - Verify that the command 'subscription-manager-migration' isn't too old. - """ - status, err = check_package_version('subscription-manager-migration', required_version) - - return (status, err) - - -def check_subman_version(required_version): - """ - Verify that the command 'subscription-manager' isn't too old. - """ - status, _ = check_package_version('subscription-manager', required_version) - - return status - - -def check_package_version(package_name, package_version): +def enable_service(service, failonerror=True): """ - Verify that the version of a package + Helper function to enable a service using proper init system. + pass failonerror = False to make init system's commands non-fatal """ - 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) + if os.path.exists("/run/systemd"): + exec_command("systemctl enable %s" % (service), not failonerror) + else: + exec_command("chkconfig %s on" % (service), not failonerror) -def setup_yum_repo(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') - call_yum('makecache') - - -def install_prereqs(): +def exec_service(service, command, failonerror=True): """ - 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 + Helper function to call a service command using proper init system. + Available command values = start, stop, restart + pass failonerror = False to make init system's commands non-fatal """ - print_generic("Checking subscription manager prerequisites") - if options.deps_repository_url: - print_generic("Enabling %s as a repository for dependency RPMs" % options.deps_repository_url) - setup_yum_repo(options.deps_repository_url, options.deps_repository_gpg_key) - if USE_YUM: - yum_base = yum.YumBase() - pkg_list = yum_base.doPackageLists(patterns=['subscription-manager']) - subman_installed = pkg_list.installed - subman_available = pkg_list.available - else: - dnf_base = dnf.Base() - dnf_base.conf.read() - dnf_base.conf.substitutions.update_from_etc('/') - dnf_base.read_all_repos() - dnf_base.fill_sack() - pkg_list = dnf_base.sack.query().filter(name='subscription-manager') - subman_installed = pkg_list.installed().run() - subman_available = pkg_list.available().run() - call_yum("remove", "subscription-manager-gnome", False) - if subman_installed: - if check_rhn_registration() and 'migration' not in options.skip: - print_generic("installing subscription-manager-migration") - call_yum("install", "'subscription-manager-migration-*'", False) - print_generic("subscription-manager is installed already. Attempting update") - call_yum("update", "subscription-manager", False) - call_yum("update", "'subscription-manager-migration-*'", False) - elif subman_available: - print_generic("subscription-manager NOT installed. Installing.") - call_yum("install", "subscription-manager") - call_yum("install", "'subscription-manager-migration-*'", False) + if os.path.exists("/run/systemd"): + exec_command("systemctl %s %s" % (command, service), not failonerror) 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 options.skip: - call_yum("update", "yum openssl python", False) - if options.deps_repository_url: - delete_file('/etc/yum.repos.d/katello-client-bootstrap-deps.repo') + exec_command("service %s %s" % (service, command), not failonerror) + + +class OSHandler(object): + """ + An abstract class that handles the interaction and configuration of the + operating system used. It defines general functions and abstract function + bodies which need system-specific implementations. + """ + + 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] + self._repo_base_url = "pulp/repos" + + ## + # Empty Function Bodies + ## + + def _check_parameter(self): + """Check input parameters""" + pass + + 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 from the given 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. + """ + pass + + def _pm_clean_cache(self): + """ + Clean the cache of the system specific package manager. + """ + pass + + def _pm_install(self, packages="", params="", fail_on_error=True): + """ + Use the system specific package manager to install packages. + """ + pass + + def _pm_install_no_gpg(self, packages="", params="", fail_on_error=True): + """ + Use the system specific package manager to install packages. + """ + pass + + def _pm_make_cache(self): + """ + Make the system specific package manager cache the current repos. + """ + pass + + def _pm_remove(self, packages, fail_on_error=True): + """ + Use the system specific package manager to remove packages. + """ + pass + + def _pm_update(self, packages="", fail_on_error=True): + """ + Use the system specific package manager to update packages. + """ + pass + + def _post_registration(self): + """ + Make configurations after the registration finished. + """ + 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 _check_imports(cls, mandatory_modules): + """ + Check whether the required imports are satisfied. + """ + 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 _check_package_version(cls, package_name, package_version): + """ + Verify the version of a package. + """ + 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): + """ + Disable the RHN plugin + """ + if os.path.exists('/etc/yum/pluginconf.d/rhnplugin.conf'): + rhnpluginconfig = SafeConfigParser() + rhnpluginconfig.read('/etc/yum/pluginconf.d/rhnplugin.conf') + if rhnpluginconfig.get('main', 'enabled') == '1': + print_generic("RHN yum plugin was enabled. Disabling...") + rhnpluginconfig.set('main', 'enabled', '0') + rhnpluginconfig.write(open('/etc/yum/pluginconf.d/rhnplugin.conf', 'w')) + 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): + """ + Enable and restart the rhsmcertd service + """ + enable_service("rhsmcertd") + exec_service("rhsmcertd", "restart") + + def _fully_update_the_box(self): + """Upgrade the host.""" + print_generic("Fully Updating The Box") + self._pm_update() + + def _generate_katello_facts(self): + """ + Write katello_facts file based on FQDN. Done after installation + of katello-ca-consumer in case the script is overriding the + FQDN. Place the location if the location option is included + """ + + print_generic("Writing FQDN katello-fact") + katellofacts = open('/etc/rhsm/facts/katello.facts', 'w') + katellofacts.write('{"network.hostname-override":"%s"}\n' % (FQDN)) + katellofacts.close() + + if self._options.location and 'foreman' in self._options.skip: + print_generic("Writing LOCATION RHSM fact") + locationfacts = open('/etc/rhsm/facts/location.facts', 'w') + locationfacts.write('{"foreman_location":"%s"}\n' % (self._options.location)) + locationfacts.close() + + @classmethod + def _get_rhsm_proxy(cls): + """ + Return the proxy server settings from /etc/rhsm/rhsm.conf as dictionary proxy_config. + """ + 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") -def is_fips(): - """ - 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" + 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") + + def _install_ssh_key_from_api(self): + """ + Download and install Server's SSH public keys. + """ + print_generic("Fetching Remote Execution SSH keys from the Server API") + url = "https://" + str(self._options.foreman_fqdn) + ":" + str(API_PORT) + "/api/v2/smart_proxies/" + 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']) + + def _install_ssh_key_from_string(self, foreman_ssh_key): + """ + Install the Server's SSH public key into the user's + authorized keys file location, so that remote execution becomes possible. + If not set default is ~/.ssh/authorized_keys + """ + print_generic("Installing Remote Execution SSH key for user %s" % self._options.remote_exec_user) + foreman_ssh_key = foreman_ssh_key.strip() + userpw = pwd.getpwnam(options.remote_exec_user) + if not self._options.remote_exec_authpath: + self._options.remote_exec_authpath = os.path.join(userpw.pw_dir, '.ssh', 'authorized_keys') + foreman_ssh_dir = os.path.join(userpw.pw_dir, '.ssh') + if not os.path.isdir(foreman_ssh_dir): + os.mkdir(foreman_ssh_dir, OWNER_ONLY_DIR) + os.chown(foreman_ssh_dir, userpw.pw_uid, userpw.pw_gid) + elif os.path.exists(self._options.remote_exec_authpath) and not os.path.isfile(self._options.remote_exec_authpath): + print_error("Server's SSH key not installed. You need to provide a full path to an authorized_keys file, you provided: '%s'" % self._options.remote_exec_authpath) + return + if os.path.isfile(self._options.remote_exec_authpath): + if foreman_ssh_key in open(self._options.remote_exec_authpath, 'r').read(): + print_generic("Server's SSH key already present in %s" % self._options.remote_exec_authpath) + return + output = os.fdopen(os.open(self._options.remote_exec_authpath, os.O_WRONLY | os.O_CREAT, OWNER_ONLY_FILE), 'a') + output.write("\n") + output.write(foreman_ssh_key) + os.chown(self._options.remote_exec_authpath, userpw.pw_uid, userpw.pw_gid) + print_generic("Server's SSH key added to %s" % self._options.remote_exec_authpath) + output.close() + + # 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 Server'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/%s" % (self._options.foreman_fqdn, self._repo_base_url), 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 the server 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/%s" % (self._options.foreman_fqdn, API_PORT, self._options.foreman_fqdn, self._repo_base_url) + 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""" + pass + + @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, 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() + global API_PORT + API_PORT = self.get_api_port() + 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) -def get_rhsm_proxy(): - """ - Return the proxy server settings from /etc/rhsm/rhsm.conf as dictionary proxy_config. - """ - 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 + # 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() + global API_PORT + API_PORT = self.get_api_port() -def set_rhsm_proxy(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')) + 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) -def get_bootstrap_rpm(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. - """ - if clean: - 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') - unregister_system() - if 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" % 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" % options.foreman_fqdn) + # 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)) -def disable_rhn_plugin(): - """ - Disable the RHN plugin for Yum - """ - if os.path.exists('/etc/yum/pluginconf.d/rhnplugin.conf'): - rhnpluginconfig = SafeConfigParser() - rhnpluginconfig.read('/etc/yum/pluginconf.d/rhnplugin.conf') - if rhnpluginconfig.get('main', 'enabled') == '1': - print_generic("RHN yum plugin was enabled. Disabling...") - rhnpluginconfig.set('main', 'enabled', '0') - rhnpluginconfig.write(open('/etc/yum/pluginconf.d/rhnplugin.conf', 'w')) - if os.path.exists('/etc/sysconfig/rhn/systemid'): - os.rename('/etc/sysconfig/rhn/systemid', '/etc/sysconfig/rhn/systemid.bootstrap-bak') - - -def enable_rhsmcertd(): - """ - Enable and restart the rhsmcertd service - """ - enable_service("rhsmcertd") - exec_service("rhsmcertd", "restart") + 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") -def is_registered(): - """ - 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')) + # 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.") -def migrate_systems(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 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") - options.rhsmargs += " --force --destination-url=https://%s:%s/rhsm" % (options.foreman_fqdn, API_PORT) - if options.legacy_purge: - options.rhsmargs += " --legacy-user '%s' --legacy-password '%s'" % (options.legacy_login, options.legacy_password) - if options.removepkgs and check_migration_version(SUBSCRIPTION_MANAGER_MIGRATION_REMOVE_PKGS_VERSION)[0]: - options.rhsmargs += " --remove-rhn-packages" - else: - options.rhsmargs += " --keep" - if check_subman_version(SUBSCRIPTION_MANAGER_SERVER_TIMEOUT_VERSION): - exec_failok("/usr/sbin/subscription-manager config --server.server_timeout=%s" % options.timeout) - exec_command("/usr/sbin/rhn-migrate-classic-to-rhsm --org %s --activation-key '%s' %s" % (org_label, activationkey, options.rhsmargs), options.ignore_registration_failures) - exec_command("subscription-manager config --rhsm.baseurl=https://%s/pulp/repos" % options.foreman_fqdn, options.ignore_registration_failures) - if options.release: - exec_failexit("subscription-manager release --set %s" % options.release) - 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. - disable_rhn_plugin() - - -def register_systems(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 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") - options.smargs += " --serverurl=https://%s:%s/rhsm --baseurl=https://%s/pulp/repos" % (options.foreman_fqdn, API_PORT, options.foreman_fqdn) - if options.force: - options.smargs += " --force" - if options.release: - options.smargs += " --release %s" % options.release - if check_subman_version(SUBSCRIPTION_MANAGER_SERVER_TIMEOUT_VERSION): - exec_failok("/usr/sbin/subscription-manager config --server.server_timeout=%s" % options.timeout) - exec_command("/usr/sbin/subscription-manager register --org '%s' --name '%s' --activationkey '%s' %s" % (org_label, FQDN, activationkey, options.smargs), options.ignore_registration_failures) - enable_rhsmcertd() - - -def unregister_system(): - """Unregister the host using `subscription-manager`.""" - print_generic("Cleaning old yum metadata") - call_yum("clean", "metadata dbcache", False) - print_generic("Unregistering") - exec_failok("/usr/sbin/subscription-manager unregister") - exec_failok("/usr/sbin/subscription-manager clean") - - -def clean_katello_agent(): - """Remove old Katello agent (aka Gofer) and certificate RPMs.""" - print_generic("Removing old Katello agent and certs") - 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 install_katello_agent(): - """Install Katello agent (aka Gofer) and activate /start it.""" - print_generic("Installing the Katello agent") - call_yum("install", "katello-agent") - enable_service("goferd") - exec_service("goferd", "restart") - - -def install_katello_host_tools(): - """Install Katello Host Tools""" - print_generic("Installing the Katello Host Tools") - call_yum("install", "katello-host-tools") - - -def clean_puppet(): - """Remove old Puppet Agent and its configuration""" - print_generic("Cleaning old Puppet Agent") - call_yum("remove", "puppet-agent", False) - delete_directory("/var/lib/puppet/") - delete_directory("/opt/puppetlabs/puppet/cache") - delete_directory("/etc/puppetlabs/puppet/ssl") - - -def clean_environment(): - """ - 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) + @classmethod + def check_package_installed(cls): + """Check if the machine already has Katello/Spacewalk 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, 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() -def generate_katello_facts(): - """ - Write katello_facts file based on FQDN. Done after installation - of katello-ca-consumer RPM in case the script is overriding the - FQDN. Place the location if the location option is included - """ + _, versionerr = self._check_migration_version(SUBSCRIPTION_MANAGER_MIGRATION_MINIMAL_VERSION) + if versionerr: + print_error(versionerr) + sys.exit(1) - print_generic("Writing FQDN katello-fact") - katellofacts = open('/etc/rhsm/facts/katello.facts', 'w') - katellofacts.write('{"network.hostname-override":"%s"}\n' % (FQDN)) - katellofacts.close() + self._install_client_ca(clean=self._options.force) + self._generate_katello_facts() + global API_PORT + API_PORT = self.get_api_port() + 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 options.location and 'foreman' in options.skip: - print_generic("Writing LOCATION RHSM fact") - locationfacts = open('/etc/rhsm/facts/location.facts', 'w') - locationfacts.write('{"foreman_location":"%s"}\n' % (options.location)) - locationfacts.close() - - -def install_puppet_agent(): - """Install and configure, then enable and start the Puppet Agent""" - puppet_env = return_puppetenv_for_hg(return_matching_foreman_key('hostgroups', 'title="%s"' % options.hostgroup, 'id', False)) - print_generic("Installing the Puppet Agent") - call_yum("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 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, options.puppet_ca_server, FQDN.lower(), puppet_env, options.puppet_server)) - if options.puppet_ca_port: - puppet_conf.write("""ca_port = %s -""" % (options.puppet_ca_port)) - if options.puppet_noop: - puppet_conf.write("""noop = true -""") - puppet_conf.close() - noop_puppet_signing_run() - if 'puppet-enable' not in options.skip: - enable_service("puppet") - exec_service("puppet", "restart") + 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() -def noop_puppet_signing_run(): - """ - 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 options.skip: - enable_service("puppet") - exec_service("puppet", "restart") + 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() + + # only do the certificate magic if 69.pem is not present + # and we have active channels + if self.check_rhn_registration() and not os.path.exists('/etc/pki/product/69.pem'): + # pylint:disable=W,C,R + _LIBPATH = "/usr/share/rhsm" + # add to the path if need be + if _LIBPATH not in sys.path: + sys.path.append(_LIBPATH) + from subscription_manager.migrate import migrate # pylint:disable=import-error + + class MEOptions: + force = True + + me = migrate.MigrationEngine() + me.options = MEOptions() + subscribed_channels = me.get_subscribed_channels_list() + me.print_banner(("System is currently subscribed to these RHNClassic Channels:")) + for channel in subscribed_channels: + print(channel) + me.check_for_conflicting_channels(subscribed_channels) + me.deploy_prod_certificates(subscribed_channels) + me.clean_up(subscribed_channels) + + # at this point we should have at least 69.pem available, but lets + # doublecheck and copy it manually if not + if not os.path.exists('/etc/pki/product/'): + os.mkdir("/etc/pki/product/") + mapping_file = "/usr/share/rhsm/product/RHEL-5/channel-cert-mapping.txt" + if not os.path.exists('/etc/pki/product/69.pem') and os.path.exists(mapping_file): + for line in open(mapping_file): + if line.startswith('rhel-%s-server-5' % ARCHITECTURE): + cert = line.split(" ")[1] + shutil.copy('/usr/share/rhsm/product/RHEL-5/' + cert.strip(), + '/etc/pki/product/69.pem') + break + + # cleanup + self._disable_rhn_plugin() + + ## + # Parent Functions + ## + + 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 _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", False) + delete_file("/etc/rhsm/ca/katello-server-ca.pem") + + def _configure_subscription_manager(self): + """ + Configure subscription-manager plugins in Yum + """ + 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')) + + 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 _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. + """ + 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): + """ + 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 + """ + 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) + if USE_YUM: + yum_base = yum.YumBase() + pkg_list = yum_base.doPackageLists(patterns=['subscription-manager']) + subman_installed = pkg_list.installed + subman_available = pkg_list.available + else: + dnf_base = dnf.Base() + dnf_base.conf.read() + dnf_base.conf.substitutions.update_from_etc('/') + dnf_base.read_all_repos() + if self._options.only_deps_repository: + save = dnf_base.repos['kt-bootstrap'] + dnf_base.repos.clear() + dnf_base.repos['kt-boostrap'] = save + dnf_base.fill_sack() + pkg_list = dnf_base.sack.query().filter(name='subscription-manager') + subman_installed = pkg_list.installed().run() + subman_available = pkg_list.available().run() + self._pm_remove("subscription-manager-gnome", False) + if 'ol' in self.os_name: + self._pm_remove("rhn-*", False) + if subman_installed: + if self.check_rhn_registration() and 'rhel' in self.os_name and 'migration' not in self._options.skip: + print_generic("installing subscription-manager-migration") + self._pm_install("subscription-manager-migration-*", fail_on_error=False) + print_generic("subscription-manager is installed already. Attempting update") + self._pm_update("subscription-manager", False) + if 'rhel' in self.os_name: + self._pm_update("subscription-manager-migration-*", False) + elif subman_available: + print_generic("subscription-manager NOT installed. Installing.") + self._pm_install("subscription-manager") + if 'rhel' in self.os_name: + self._pm_install("subscription-manager-migration-*", fail_on_error=False) + 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 _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, params, 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: + pack_str = packages + self._call_yum("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_yum("update %s" % pack_str, "", fail_on_error) + + 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) + + +class OSHandlerSLES(OSHandler): + """ + OSHandler implementation for SLES and SUSE systems. + """ + + def __init__(self, in_options): + OSHandler.__init__(self, in_options) + mandatory_modules = {"rpm": "python-rpm"} + self._check_imports(mandatory_modules) + self._check_parameter() + + ## + # Inner Helper Functions + ## + + @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)[0] + + 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) + + 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) + + ## + # Parent Functions + ## + + def classic_migration(self): + """ + Migration via rhn-classic-migrate-to-rhsm is not supported on SLES. + """ + print_generic("Classic migration is not supported on SLES systems.") + + def prepare_migration(self): + """ + Preparation of migration when existing system shall not be removed and no new capsule is used. + """ + pass + + def _check_parameter(self): + """Check input parameters""" + 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("") + + if not self._options.deps_repository_url: + print_error("Repository URL not given! Parameter deps-repository-url is mandatory") + sys.exit(1) -def remove_obsolete_packages(): - """Remove old RHN packages""" - print_generic("Removing old RHN packages") - 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) + 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*", False) + delete_file("/etc/rhsm/ca/katello-server-ca.pem") + + 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 given server. + Uses --insecure options to curl(1) if instructed to download via HTTPS. + """ + 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.os_version == 11: + if self._options.download_method == "https": + print_generic("Retrieving Client CA Certificate with wget and --no-check-certificate") + exec_failexit("wget https://%s/pub/katello-rhsm-consumer --no-check-certificate" % self._options.foreman_fqdn) + else: + print_generic("Retrieving Client CA Certificate") + exec_failexit("wget http://%s/pub/katello-rhsm-consumer" % self._options.foreman_fqdn) + + print_generic("Add katello certificates") + exec_failexit("/bin/bash -x katello-rhsm-consumer", suppress_output=True) + delete_file("./katello-rhsm-consumer") + elif 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_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) + 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) + + 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): + """ + Install subscription manager and its prerequisites. + """ + 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 curl", 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 _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: + pack_str = packages + self._call_zypper("install %s" % pack_str, params, fail_on_error) + + 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 _pm_make_cache(self): + """ + Create zypper cache. + """ + pass + + 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 zypper 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 OSHandlerDeb(OSHandler): + """ + OSHandler implementation for Debian and Ubuntu systems. + """ + + def __init__(self, in_options): + OSHandler.__init__(self, in_options) + self._repo_base_url = "pulp/deb" + + ## + # Inner Helper Functions + ## + + @classmethod + def _call_apt(cls, command, params="", fail_on_error=True): + """ + Helper function to call a apt command on a list of packages. + pass fail_on_error=False to make apt commands non-fatal. + """ + exec_command("/usr/bin/apt-get --quiet -y %s %s" % (params, command), not fail_on_error) + + @classmethod + def _call_apt_cache(cls, params="gencaches", fail_on_error=False): + """ + Helper function to call apt-cache. + """ + exec_command("/usr/bin/apt-cache %s" % params, not fail_on_error) + + def _setup_repo(self, repo_url, gpg_url): + """ + Add Debian repo to install subscription-manager and katello-agent + """ + print_generic("Renaming old rhsm.sources to rhsm.sources.bak") + exec_failok("mv -f /etc/apt/sources.list.d/rhsm.sources /etc/apt/sources.list.d/rhsm.sources.bak") + + print_generic("Retrieve GPG key from URL") + exec_failexit("wget %s --no-check-certificate" % gpg_url) + key_name = gpg_url.split("/")[-1] + exec_failexit("apt-key add %s " % key_name) + delete_file("./%s" % key_name) + + print_generic("Disable all other repos") + exec_command("echo \"# This system is managed by the subscription-manager\" > /etc/apt/sources.list") + + print_generic("Add subscription-manager repository") + exec_failexit("echo deb %s default all > /etc/apt/sources.list.d/tmp_subman.list" % repo_url) + print_generic("Update Repository") + self._pm_make_cache() + + ## + # Parent Functions + ## + + def check_package_installed(self): + """ + Check if the machine already has Katello/Spacewalk installed + """ + print_generic("Check for installed Katello/Spacewalk packages") + banned_packages = ['katello', 'foreman-proxy-content', 'katello-capsule', 'spacewalk-proxy-common', 'spacewalk-backend'] + for package in banned_packages: + ret_code, _ = exec_failok("dpkg-query --showformat='${Version}' --show %s" % package, suppress_output=True) + if ret_code == 0: + print_error("%s package found. bootstrap.py should not be used on a Katello/Spacewalk/Satellite host." % (package)) + sys.exit(1) + def classic_migration(self): + """ + Migration via rhn-classic-migrate-to-rhsm is not supported on Debian. + """ + print_generic("Classic migration is not supported on Debian systems.") + + def prepare_migration(self): + """ + Preparation of migration when existing system shall not be removed and no new capsule is used. + """ + pass + + def _check_package_version(self, package_name, package_version): + """ + Verify the version of a package. + """ + (ret_code, output) = exec_failok("dpkg-query --showformat='${Version}' --show %s" % package_name) + if ret_code != 0: + err = "%s not found" % package_name + elif output == "": + err = "% found, but version cannot be determined" % package_name + elif exec_failok("dpkg --compare-versions %s \"lt\" %s" % (output, package_version))[0] > 0: + err = "%s %s is too old" % (package_name, output) + else: + err = None -def fully_update_the_box(): - """Call `yum -y update` to upgrade the host.""" - print_generic("Fully Updating The Box") - call_yum("update") + return (err is None, err) + def _check_parameter(self): + """Check input parameters""" + if not self._options.deps_repository_gpg_key: + print_error("GPG URL not given! Parameter deps-repository-gpg-key is mandatory") + sys.exit(1) + if not self._options.deps_repository_url: + print_error("Repository URL not given! Parameter deps-repository-url is mandatory") + sys.exit(1) -# curl https://satellite.example.com:9090/ssh/pubkey >> ~/.ssh/authorized_keys -# sort -u ~/.ssh/authorized_keys -def install_ssh_key_from_url(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) + def _check_subman_version(self, required_version): + """ + Verify that the command 'subscription-manager' isn't too old. + """ + status, _ = self._check_package_version('python-subscription-manager', required_version) + + return status + + def _clean_katello_agent(self): + """Remove old certificate.""" + print_generic("Removing old certs") + delete_file("/etc/rhsm/ca/katello-server-ca.pem") + + def _configure_subscription_manager(self): + """ + Configure subscription-manager. + """ + pass + + def _install_client_ca(self, clean=False, unreg=True): + """ + Retrieve Client CA Certificate from the given server. + Uses --no-check-certificate option to wget(1) if instructed to download via HTTPS. + """ + 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("Retrieving Client CA Certificate with wget and --no-check-certificate") + exec_failexit("wget https://%s/pub/katello-rhsm-consumer --no-check-certificate" % self._options.foreman_fqdn) 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 - install_ssh_key_from_string(foreman_ssh_key) + print_generic("Retrieving Client CA Certificate") + exec_failexit("wget http://%s/pub/katello-rhsm-consumer" % self._options.foreman_fqdn) + print_generic("Add katello certificates") + exec_failexit("/bin/bash -x katello-rhsm-consumer", suppress_output=True) + delete_file("./katello-rhsm-consumer") -def install_ssh_key_from_api(): - """ - Download and install all Foreman's SSH public keys. - """ - print_generic("Fetching Remote Execution SSH keys from the Foreman API") - url = "https://" + options.foreman_fqdn + ":" + str(API_PORT) + "/api/v2/smart_proxies/" - smart_proxies = get_json(url) - for smart_proxy in smart_proxies['results']: - if 'remote_execution_pubkey' in smart_proxy: - install_ssh_key_from_string(smart_proxy['remote_execution_pubkey']) + print_generic("Create symlinks for system wide CA usage") + try: + # Determine root certificates + default_root_cert = find_root_cert("/etc/rhsm/ca/katello-default-ca.pem") + server_root_cert = find_root_cert("/etc/rhsm/ca/katello-server-ca.pem") + with open("/usr/local/share/ca-certificates/katello-default-ca.crt", "w+") as default_file: + default_file.write(default_root_cert) + with open("/usr/local/share/ca-certificates/katello-server-ca.crt", "w+") as server_file: + server_file.write(server_root_cert) + exec_failexit("update-ca-certificates") + except Exception: + print_error("An error occured while updating the certificates:\n %s" % sys.exc_info()[1]) + sys.exit(1) + def _install_katello_agent(self): + """Katello agent (aka Gofer) is not supported on Debian systems.""" + pass + + def _install_katello_host_tools(self): + """Katello host tools are not supported on Debian systems""" + pass + + def _install_prereqs(self): + """ + Install subscription manager and its prerequisites. + """ + self._pm_install("wget httpie jq gnupg", fail_on_error=True) + self._setup_repo(self._options.deps_repository_url, self._options.deps_repository_gpg_key) + self._pm_install("subscription-manager apt-transport-katello katello-upload-profile", fail_on_error=True) + + if 'prereq-update' not in self._options.skip: + self._pm_update("openssl python", False) + + def _pm_clean_cache(self): + """ + Clean cache of apt. + """ + self._call_apt("clean") + + def _pm_install(self, packages="", params="", fail_on_error=True): + """ + Use apt to install packages. + """ + if isinstance(packages, list): + pack_str = " ".join(packages) + else: + pack_str = packages + self._call_apt("install %s" % pack_str, params, fail_on_error) + + def _pm_install_no_gpg(self, packages="", params="", fail_on_error=True): + """ + Use apt to install packages without checking the gpg key. + """ + self._pm_install(packages, ("--allow-unauthenticated %s" % params), fail_on_error) + + def _pm_make_cache(self): + """ + Create apt cache. + """ + self._call_apt_cache("gencaches", fail_on_error=False) + self._call_apt("update") + + def _pm_remove(self, packages="", fail_on_error=True): + """ + Use apt to remove packages. + """ + if isinstance(packages, list): + pack_str = " ".join(packages) + else: + pack_str = packages + self._call_apt("purge %s" % pack_str, "", fail_on_error) + + def _pm_update(self, packages="", fail_on_error=True): + """ + Use apt to update specific packages. + """ + if isinstance(packages, list): + pack_str = " ".join(packages) + else: + pack_str = packages + self._call_apt("upgrade %s" % pack_str, "", fail_on_error) -def install_ssh_key_from_string(foreman_ssh_key): - """ - Install Foreman's SSH public key into the foreman user's - authorized keys file location, so that remote execution becomes possible. - If not set default is ~/.ssh/authorized_keys - """ - print_generic("Installing Remote Execution SSH key for user %s" % options.remote_exec_user) - foreman_ssh_key = foreman_ssh_key.strip() - userpw = pwd.getpwnam(options.remote_exec_user) - if not options.remote_exec_authpath: - options.remote_exec_authpath = os.path.join(userpw.pw_dir, '.ssh', 'authorized_keys') - foreman_ssh_dir = os.path.join(userpw.pw_dir, '.ssh') - if not os.path.isdir(foreman_ssh_dir): - os.mkdir(foreman_ssh_dir, OWNER_ONLY_DIR) - os.chown(foreman_ssh_dir, userpw.pw_uid, userpw.pw_gid) - elif os.path.exists(options.remote_exec_authpath) and not os.path.isfile(options.remote_exec_authpath): - print_error("Foreman's SSH key not installed. You need to provide a full path to an authorized_keys file, you provided: '%s'" % options.remote_exec_authpath) - return - if os.path.isfile(options.remote_exec_authpath): - if foreman_ssh_key in open(options.remote_exec_authpath, 'r').read(): - print_generic("Foreman's SSH key already present in %s" % options.remote_exec_authpath) - return - output = os.fdopen(os.open(options.remote_exec_authpath, os.O_WRONLY | os.O_CREAT, OWNER_ONLY_FILE), 'a') - output.write("\n") - output.write(foreman_ssh_key) - output.write("\n") - os.chown(options.remote_exec_authpath, userpw.pw_uid, userpw.pw_gid) - print_generic("Foreman's SSH key added to %s" % options.remote_exec_authpath) - output.close() + def _post_registration(self): + """ + Make configurations after the registration finished. + """ + delete_file("/etc/apt/sources.list.d/tmp_subman.list") + self._call_apt("update") + exec_command("/usr/bin/package-profile-upload") class BetterHTTPErrorProcessor(urllib_basehandler): @@ -748,7 +1814,7 @@ def call_api(url, data=None, method='GET'): try: jsonerr = json.load(exception) print('error: %s' % json.dumps(jsonerr, sort_keys=False, indent=2)) - except: # noqa: E722, pylint:disable=bare-except + except Exception: # noqa: E722, pylint:disable=broad-except print('error: %s' % exception) sys.exit(1) except Exception: # pylint:disable=broad-except @@ -781,7 +1847,7 @@ def update_host_capsule_mapping(attribute, capsule_id, host_id): """ Update the host entry to point a feature to a new proxy """ - url = "https://" + options.foreman_fqdn + ":" + str(API_PORT) + "/api/v2/hosts/" + str(host_id) + url = "https://" + str(options.foreman_fqdn) + ":" + str(API_PORT) + "/api/v2/hosts/" + str(host_id) if attribute == 'content_source_id': jdata = {"host": {"content_facet_attributes": {"content_source_id": capsule_id}, "content_source_id": capsule_id}} else: @@ -793,7 +1859,7 @@ def get_capsule_features(capsule_id): """ Fetch all features available on a proxy """ - url = "https://" + options.foreman_fqdn + ":" + str(API_PORT) + "/katello/api/capsules/%s" % str(capsule_id) + url = "https://" + str(options.foreman_fqdn) + ":" + str(API_PORT) + "/katello/api/capsules/%s" % str(capsule_id) return [feature['name'] for feature in get_json(url)['features']] @@ -802,7 +1868,7 @@ def update_host_config(attribute, value, host_id): Update a host config """ attribute_id = return_matching_foreman_key(attribute + 's', 'title="%s"' % value, 'id', False) - json_key = attribute + "_id" + json_key = str(attribute) + "_id" jdata = {"host": {json_key: attribute_id}} put_json("https://" + options.foreman_fqdn + ":" + API_PORT + "/api/hosts/%s" % host_id, jdata) @@ -829,7 +1895,7 @@ def return_matching_key(api_path, search_key, return_key, null_result_ok=False): the key for search (name=, title=, ...). The search_key must be quoted in advance. """ - myurl = "https://" + options.foreman_fqdn + ":" + API_PORT + api_path + "/?" + urlencode([('search', '' + str(search_key))]) + myurl = "https://" + str(options.foreman_fqdn) + ":" + str(API_PORT) + str(api_path) + "/?" + str(urlencode([('search', '' + str(search_key))])) return_values = get_json(myurl) result_len = len(return_values['results']) if result_len == 1: @@ -850,7 +1916,7 @@ def return_puppetenv_for_hg(hg_id): or inherited through its hierarchy. If no environment is found, "production" is assumed. """ - myurl = "https://" + options.foreman_fqdn + ":" + API_PORT + "/api/v2/hostgroups/" + str(hg_id) + myurl = "https://" + str(options.foreman_fqdn) + ":" + str(API_PORT) + "/api/v2/hostgroups/" + str(hg_id) hostgroup = get_json(myurl) environment_name = 'production' if hostgroup['environment_name']: @@ -866,7 +1932,7 @@ def create_domain(domain, orgid, locid): Call Foreman API to create a network domain associated with the given organization and location. """ - myurl = "https://" + options.foreman_fqdn + ":" + API_PORT + "/api/v2/domains" + myurl = "https://" + str(options.foreman_fqdn) + ":" + str(API_PORT) + "/api/v2/domains" domid = return_matching_foreman_key('domains', 'name="%s"' % domain, 'id', True) if not domid: jsondata = {"domain": {"name": domain, "organization_ids": [orgid], "location_ids": [locid]}} @@ -948,7 +2014,7 @@ def create_host(): def delete_host(host_id): """Call Foreman API to delete the current host.""" - myurl = "https://" + options.foreman_fqdn + ":" + API_PORT + "/api/v2/hosts/" + myurl = "https://" + str(options.foreman_fqdn) + ":" + str(API_PORT) + "/api/v2/hosts/" print_running("Deleting host id %s for host %s" % (host_id, FQDN)) delete_json("%s/%s" % (myurl, host_id)) @@ -957,176 +2023,32 @@ def disassociate_host(host_id): """ Call Foreman API to disassociate host from content host before deletion. """ - myurl = "https://" + options.foreman_fqdn + ":" + API_PORT + "/api/v2/hosts/" + str(host_id) + "/disassociate" + myurl = "https://" + str(options.foreman_fqdn) + ":" + str(API_PORT) + "/api/v2/hosts/" + str(host_id) + "/disassociate" print_running("Disassociating host id %s for host %s" % (host_id, FQDN)) put_json(myurl) -def configure_subscription_manager(): - """ - Configure subscription-manager plugins in Yum - """ - 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')) - - 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 check_rhn_registration(): - """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 - - -def enable_repos(): - """Enable necessary repositories using subscription-manager.""" - repostoenable = " ".join(['--enable=%s' % i for i in options.enablerepos.split(',')]) - print_running("Enabling repositories - %s" % options.enablerepos) - exec_failok("subscription-manager repos %s" % repostoenable) - - -def install_packages(): - """Install user-provided packages""" - packages = options.install_packages.replace(',', " ") - print_running("Installing the following packages %s" % packages) - call_yum("install", packages, False) - - -def get_api_port(): - """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: # noqa: E722, pylint:disable=bare-except - return "443" - - -def check_rpm_installed(): - """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(): - """ - 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. - """ - install_prereqs() - - # only do the certificate magic if 69.pem is not present - # and we have active channels - if check_rhn_registration() and not os.path.exists('/etc/pki/product/69.pem'): - # pylint:disable=W,C,R - _LIBPATH = "/usr/share/rhsm" - # add to the path if need be - if _LIBPATH not in sys.path: - sys.path.append(_LIBPATH) - from subscription_manager.migrate import migrate # pylint:disable=import-error - - class MEOptions: - force = True - - me = migrate.MigrationEngine() - me.options = MEOptions() - subscribed_channels = me.get_subscribed_channels_list() - me.print_banner(("System is currently subscribed to these RHNClassic Channels:")) - for channel in subscribed_channels: - print(channel) - me.check_for_conflicting_channels(subscribed_channels) - me.deploy_prod_certificates(subscribed_channels) - me.clean_up(subscribed_channels) - - # at this point we should have at least 69.pem available, but lets - # doublecheck and copy it manually if not - if not os.path.exists('/etc/pki/product/'): - os.mkdir("/etc/pki/product/") - mapping_file = "/usr/share/rhsm/product/RHEL-5/channel-cert-mapping.txt" - if not os.path.exists('/etc/pki/product/69.pem') and os.path.exists(mapping_file): - for line in open(mapping_file): - if line.startswith('rhel-%s-server-5' % ARCHITECTURE): - cert = line.split(" ")[1] - shutil.copy('/usr/share/rhsm/product/RHEL-5/' + cert.strip(), - '/etc/pki/product/69.pem') - break - - # cleanup - disable_rhn_plugin() - - -def enable_service(service, failonerror=True): - """ - Helper function to enable a service using proper init system. - pass failonerror = False to make init system's commands non-fatal - """ - if os.path.exists("/run/systemd"): - exec_command("/usr/bin/systemctl enable %s" % (service), not failonerror) - else: - exec_command("/sbin/chkconfig %s on" % (service), not failonerror) - - -def exec_service(service, command, failonerror=True): - """ - Helper function to call a service command using proper init system. - Available command values = start, stop, restart - pass failonerror = False to make init system's commands non-fatal - """ - if os.path.exists("/run/systemd"): - exec_command("/usr/bin/systemctl %s %s" % (command, service), not failonerror) - else: - exec_command("/sbin/service %s %s" % (service, command), not failonerror) - - if __name__ == '__main__': # pylint:disable=invalid-name 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("") # > Register our better HTTP processor as default opener for URLs. opener = urllib_build_opener(BetterHTTPErrorProcessor) urllib_install_opener(opener) # > Gather MAC Address. - MAC = None - try: - import uuid - mac1 = uuid.getnode() - mac2 = uuid.getnode() - if mac1 == mac2: - MAC = ':'.join(("%012X" % mac1)[i:i + 2] for i in range(0, 12, 2)) - except ImportError: - if os.path.exists('/sys/class/net/eth0/address'): - address_files = ['/sys/class/net/eth0/address'] - else: - address_files = glob.glob('/sys/class/net/*/address') - for f in address_files: - MAC = open(f).readline().strip().upper() - if MAC != "00:00:00:00:00:00": - break + if os.path.exists('/sys/class/net/eth0/address'): + address_files = ['/sys/class/net/eth0/address'] + else: + address_files = glob.glob('/sys/class/net/*/address') + for f in address_files: + MAC = open(f).readline().strip().upper() + if MAC != "00:00:00:00:00:00": + break if not MAC: MAC = "00:00:00:00:00:00" @@ -1134,17 +2056,10 @@ def exec_service(service, command, failonerror=True): API_PORT = "443" ARCHITECTURE = get_architecture() try: - # pylint:disable=deprecated-method - RELEASE = platform.linux_distribution()[1] - except AttributeError: - # pylint:disable=deprecated-method,no-member - 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'] @@ -1182,7 +2097,7 @@ def exec_service(service, command, failonerror=True): 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) @@ -1194,8 +2109,9 @@ def exec_service(service, command, failonerror=True): parser.add_option("--enablerepos", dest="enablerepos", help="Repositories to be enabled via subscription-manager - comma separated", metavar="enablerepos") parser.add_option("--skip", dest="skip", action="append", help="Skip the listed steps (choices: %s)" % SKIP_STEPS, choices=SKIP_STEPS, default=[]) parser.add_option("--ip", dest="ip", help="IPv4 address of the primary interface in Foreman (defaults to the address used to make request to Foreman)") - parser.add_option("--deps-repository-url", dest="deps_repository_url", help="URL to a repository that contains the subscription-manager RPMs") - parser.add_option("--deps-repository-gpg-key", dest="deps_repository_gpg_key", help="GPG Key to the repository that contains the subscription-manager RPMs", default="file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release") + parser.add_option("--only-deps-repository", dest="only_deps_repository", action="store_true", help="Use only the deps repository while installing the subscription-manager.") + parser.add_option("--deps-repository-url", dest="deps_repository_url", help="URL to a repository that contains the subscription-manager. Mandatory for: SLES, Debian, Ubuntu") + parser.add_option("--deps-repository-gpg-key", dest="deps_repository_gpg_key", help="GPG Key to the repository that contains the subscription-manager. Mandatory for: Debian, Ubuntu", default="file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release") parser.add_option("--install-packages", dest="install_packages", help="List of packages to be additionally installed - comma separated", metavar="installpackages") parser.add_option("--new-capsule", dest="new_capsule", action="store_true", help="Switch the server to a new capsule for content and Puppet. Pass --server with the Capsule FQDN as well.") parser.add_option("-t", "--timeout", dest="timeout", type="int", help="Timeout (in seconds) for API calls and subscription-manager registration. Defaults to %default", metavar="timeout", default=900) @@ -1275,7 +2191,7 @@ def exec_service(service, command, failonerror=True): s.connect((options.foreman_fqdn, 80)) options.ip = s.getsockname()[0] s.close() - except: # noqa: E722, pylint:disable=bare-except + except Exception: # noqa: E722, pylint:disable=broad-except options.ip = None # > Ask for the password if not given as option @@ -1336,8 +2252,21 @@ def exec_service(service, command, failonerror=True): print_error("This script requires root-level access") sys.exit(1) + # Create OSHandler instance depending on OS + if "suse" in OS_NAME or "sles" in OS_NAME: + os_handler = OSHandlerSLES(options) + elif "debian" in OS_NAME or "ubuntu" in OS_NAME: + os_handler = OSHandlerDeb(options) + 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) + sys.exit(1) + + IS_EL8 = os_handler.os_version == 8 + # > Check if Katello/Spacewalk/Satellite are installed already - 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 @@ -1349,7 +2278,7 @@ def exec_service(service, command, failonerror=True): import simplejson as json except ImportError: print_warning("Could neither import json nor simplejson, will try to install simplejson and re-import") - call_yum("install", "python-simplejson") + os_handler.install_simplejson() try: import simplejson as json except ImportError: @@ -1357,54 +2286,22 @@ def exec_service(service, command, failonerror=True): sys.exit(1) # > Clean the environment from LD_... variables - clean_environment() + 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.") - prepare_rhel5_migration() - - if options.preserve_rhsm_proxy: - saved_proxy_config = get_rhsm_proxy() + os_handler.prepare_migration() if options.remove: - # > IF remove, disassociate/delete host, unregister, - # > uninstall katello and optionally puppet agents - API_PORT = get_api_port() - 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: - clean_katello_agent() - if 'puppet' not in options.skip: - clean_puppet() - elif check_rhn_registration() and 'migration' not in options.skip and not IS_EL8: + API_PORT = os_handler.get_api_port() + 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, + # > get CA, 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') - install_prereqs() - - _, versionerr = check_migration_version(SUBSCRIPTION_MANAGER_MIGRATION_MINIMAL_VERSION) - if versionerr: - print_error(versionerr) - sys.exit(1) - - get_bootstrap_rpm(clean=options.force) - generate_katello_facts() - API_PORT = get_api_port() - if 'foreman' not in options.skip: - create_host() - configure_subscription_manager() - migrate_systems(options.org, options.activationkey) - if options.enablerepos: - enable_repos() + os_handler.classic_migration() elif options.new_capsule: # > ELIF new_capsule and foreman_fqdn set, will migrate to other capsule # @@ -1414,89 +2311,11 @@ def exec_service(service, command, failonerror=True): # > 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 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 - install_prereqs() - get_bootstrap_rpm(clean=True, unreg=False) - install_katello_host_tools() - if options.install_katello_agent: - 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") - enable_rhsmcertd() - - API_PORT = 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)) - - 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, + # > ELSE get CA, optionally create host, # > register via subscription-manager - print_generic('This system is not registered to RHN. Attempting to register via subscription-manager') - install_prereqs() - get_bootstrap_rpm(clean=options.force) - generate_katello_facts() - API_PORT = get_api_port() - if 'foreman' not in options.skip: - create_host() - configure_subscription_manager() - if options.preserve_rhsm_proxy: - set_rhsm_proxy(saved_proxy_config) - register_systems(options.org, options.activationkey) - if options.enablerepos: - enable_repos() - + os_handler.blank_migration() if options.location and 'foreman' in options.skip: delete_file('/etc/rhsm/facts/location.facts') @@ -1504,36 +2323,4 @@ def exec_service(service, command, failonerror=True): # > 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: - 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: - install_katello_host_tools() - if options.update: - fully_update_the_box() - - if 'puppet' not in options.skip: - if options.force: - clean_puppet() - install_puppet_agent() - - if options.install_packages: - install_packages() - - if 'remove-obsolete-packages' not in options.skip: - 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" - install_ssh_key_from_url(remote_exec_url) - elif options.remote_exec_url: - install_ssh_key_from_url(options.remote_exec_url) - elif options.remote_exec_apikeys: - install_ssh_key_from_api() - else: - remote_exec_url = "https://" + str(options.foreman_fqdn) + ":9090/ssh/pubkey" - install_ssh_key_from_url(remote_exec_url) + os_handler.clean_and_update() diff --git a/pylintrc b/pylintrc index fd6e3cd..f418c95 100644 --- a/pylintrc +++ b/pylintrc @@ -65,7 +65,7 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating,superfluous-parens +disable=ungrouped-imports,not-context-manager,global-statement,unnecessary-pass,useless-object-inheritance,deprecated-method,old-style-class,unused-argument,broad-except,import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating,superfluous-parens [REPORTS] @@ -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). @@ -160,7 +160,7 @@ ignore-mixin-members=yes # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. -ignored-modules= +ignored-modules=platform # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of @@ -343,7 +343,7 @@ max-args=6 ignored-argument-names=_.* # Maximum number of locals for function / method body -max-locals=15 +max-locals=20 # Maximum number of return / yield for function / method body max-returns=6 @@ -352,7 +352,7 @@ max-returns=6 max-branches=15 # Maximum number of statements in function / method body -max-statements=50 +max-statements=60 # Maximum number of parents for a class (see R0901). max-parents=7 @@ -364,7 +364,7 @@ max-attributes=7 min-public-methods=2 # Maximum number of public methods for a class (see R0904). -max-public-methods=20 +max-public-methods=40 # Maximum number of boolean expressions in a if statement max-bool-expr=5