From 15c6d151baecf11e5264b9385853abcc5dd6839e Mon Sep 17 00:00:00 2001 From: Eric Lavarde Date: Sun, 23 Oct 2016 09:15:02 +0200 Subject: [PATCH] Add pydoc documentation to the source, and a __main__ section to make it work. Also add '##' so that one can grep/awk for the sequence of actions taken. Add a note accordingly into README.md. --- README.md | 7 + bootstrap.py | 478 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 305 insertions(+), 180 deletions(-) diff --git a/README.md b/README.md index 55254e5..0ea6787 100644 --- a/README.md +++ b/README.md @@ -133,3 +133,10 @@ Options: --rex-user=REMOTE_EXEC_USER Local user used by Foreman's remote execution feature. ~~~ + +# For developers: + +Use `pydoc ./bootstrap.py` to get the code documentation. + +Use `awk -F\#\# 'NF>1 {print $2}' ./bootstrap.py` to see the flow of the script. + diff --git a/bootstrap.py b/bootstrap.py index 96b062b..056a89f 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -1,4 +1,11 @@ #!/usr/bin/python +""" +Script to register a new host to Foreman/Satellite +or move it from Satellite 5 to 6. + +Use `pydoc ./bootstrap.py` to get the documentation. +Use `awk -F\#\# 'NF>1 {print $2}' ./bootstrap.py` to see the flow. +""" import getpass import urllib2 @@ -19,112 +26,13 @@ from ConfigParser import SafeConfigParser def get_architecture(): + """ + Helper function to get the architecture x86_64 vs. x86. + """ return os.uname()[4] -FQDN = socket.getfqdn() -if FQDN.find(".") != -1: - HOSTNAME = FQDN.split('.')[0] - DOMAIN = FQDN[FQDN.index('.')+1:] -else: - HOSTNAME = FQDN - DOMAIN = None - -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 not MAC: - MAC = "00:00:00:00:00:00" - -API_PORT = "443" -ARCHITECTURE = get_architecture() -try: - RELEASE = platform.linux_distribution()[1] -except AttributeError: - RELEASE = platform.dist()[1] - -parser = OptionParser() -parser.add_option("-s", "--server", dest="foreman_fqdn", help="FQDN of Foreman OR Capsule - omit https://", metavar="foreman_fqdn") -parser.add_option("-l", "--login", dest="login", default='admin', help="Login user for API Calls", metavar="LOGIN") -parser.add_option("-p", "--password", dest="password", help="Password for specified user. Will prompt if omitted", metavar="PASSWORD") -parser.add_option("--legacy-login", dest="legacy_login", default='admin', help="Login user for Satellite 5 API Calls", metavar="LOGIN") -parser.add_option("--legacy-password", dest="legacy_password", help="Password for specified Satellite 5 user. Will prompt if omitted", metavar="PASSWORD") -parser.add_option("--legacy-purge", dest="legacy_purge", action="store_true", help="Purge system from the Legacy environment (e.g. Sat5)") -parser.add_option("-a", "--activationkey", dest="activationkey", help="Activation Key to register the system", metavar="ACTIVATIONKEY") -parser.add_option("-P", "--skip-puppet", dest="no_puppet", action="store_true", default=False, help="Do not install Puppet") -parser.add_option("--skip-foreman", dest="no_foreman", action="store_true", default=False, help="Do not create a Foreman host. Implies --skip-puppet. When using --skip-foreman, you MUST pass the Organization's LABEL, not NAME") -parser.add_option("-g", "--hostgroup", dest="hostgroup", help="Title of the Hostgroup in Foreman that the host is to be associated with", metavar="HOSTGROUP") -parser.add_option("-L", "--location", dest="location", help="Title of the Location in Foreman that the host is to be associated with", metavar="LOCATION") -parser.add_option("-O", "--operatingsystem", dest="operatingsystem", default=None, help="Title of the Operating System in Foreman that the host is to be associated with", metavar="OPERATINGSYSTEM") -parser.add_option("--partitiontable", dest="partitiontable", default=None, help="Name of the Partition Table in Foreman that the host is to be associated with", metavar="PARTITIONTABLE") -parser.add_option("-o", "--organization", dest="org", default='Default Organization', help="Name of the Organization in Foreman that the host is to be associated with", metavar="ORG") -parser.add_option("-S", "--subscription-manager-args", dest="smargs", default="", help="Which additional arguments shall be passed to subscription-manager", metavar="ARGS") -parser.add_option("--rhn-migrate-args", dest="rhsmargs", default="", help="Which additional arguments shall be passed to rhn-migrate-classic-to-rhsm", metavar="ARGS") -parser.add_option("-u", "--update", dest="update", action="store_true", help="Fully Updates the System") -parser.add_option("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") -parser.add_option("-f", "--force", dest="force", action="store_true", help="Force registration (will erase old katello and puppet certs)") -parser.add_option("--add-domain", dest="add_domain", action="store_true", help="Automatically add the clients domain to Foreman") -parser.add_option("--remove", dest="remove", action="store_true", help="Instead of registring the machine to Foreman remove it") -parser.add_option("-r", "--release", dest="release", default=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("--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) -parser.add_option("--rex-user", dest="remote_exec_user", default="root", help="Local user used by Foreman's remote execution feature.") -parser.add_option("--enablerepos", dest="enablerepos", help="Repositories to be enabled via subscription-manager - comma separated", metavar="enablerepos") -(options, args) = parser.parse_args() - -if not (options.foreman_fqdn and options.login and (options.remove or (options.org and options.activationkey and (options.no_foreman or options.hostgroup)))): - print "Must specify server, login, organization, hostgroup, and activation key. See usage:" - parser.print_help() - print "\nExample usage: ./bootstrap.py -l admin -s foreman.example.com -o 'Default Organization' -L 'Default Location' -g My_Hostgroup -a My_Activation_Key" - sys.exit(1) - -if not DOMAIN and not (options.force or options.no_puppet): - print "We could not determine the domain of this machine, most probably `hostname -f` does not return the FQDN." - print "This can lead to Puppet missbehaviour and thus the script will terminate now." - print "You can override this by passing --force or --skip-puppet" - sys.exit(1) - -if not options.password and not options.no_foreman: - options.password = getpass.getpass("%s's password:" % options.login) - -if options.legacy_purge and not options.legacy_password: - options.legacy_password = getpass.getpass("Legacy User %s's password:" % options.legacy_login) - -if options.no_foreman: - options.no_puppet = True - -if options.verbose: - print "HOSTNAME - %s" % HOSTNAME - print "DOMAIN - %s" % DOMAIN - print "RELEASE - %s" % RELEASE - print "MAC - %s" % MAC - print "foreman_fqdn - %s" % options.foreman_fqdn - print "LOGIN - %s" % options.login - print "PASSWORD - %s" % options.password - print "HOSTGROUP - %s" % options.hostgroup - print "LOCATION - %s" % options.location - print "OPERATINGSYSTEM - %s" % options.operatingsystem - print "PARTITIONTABLE - %s" % options.partitiontable - print "ORG - %s" % options.org - print "ACTIVATIONKEY - %s" % options.activationkey - print "UPDATE - %s" % options.update - print "LEGACY LOGIN - %s" % options.legacy_login - print "LEGACY PASSWORD - %s" % options.legacy_password +"""Colors to be used by the multiple `print_*` functions.""" error_colors = { 'HEADER': '\033[95m', 'OKBLUE': '\033[94m', @@ -136,26 +44,32 @@ def get_architecture(): def print_error(msg): + """Helper function to output an ERROR message.""" print "[%sERROR%s], [%s], EXITING: [%s] failed to execute properly." % (error_colors['FAIL'], error_colors['ENDC'], datetime.now().strftime('%Y-%m-%d %H:%M:%S'), msg) def print_warning(msg): + """Helper function to output a WARNING message.""" print "[%sWARNING%s], [%s], NON-FATAL: [%s] failed to execute properly." % (error_colors['WARNING'], error_colors['ENDC'], datetime.now().strftime('%Y-%m-%d %H:%M:%S'), msg) def print_success(msg): + """Helper function to output a SUCCESS message.""" print "[%sSUCCESS%s], [%s], [%s], completed successfully." % (error_colors['OKGREEN'], error_colors['ENDC'], datetime.now().strftime('%Y-%m-%d %H:%M:%S'), msg) def print_running(msg): + """Helper function to output a RUNNING message.""" print "[%sRUNNING%s], [%s], [%s] " % (error_colors['OKBLUE'], error_colors['ENDC'], datetime.now().strftime('%Y-%m-%d %H:%M:%S'), msg) def print_generic(msg): + """Helper function to output a NOTIFICATION message.""" print "[NOTIFICATION], [%s], [%s] " % (datetime.now().strftime('%Y-%m-%d %H:%M:%S'), msg) def exec_failok(command): + """Helper function to call a command with only warning if failing.""" print_running(command) output = commands.getstatusoutput(command) retcode = output[0] @@ -167,6 +81,7 @@ def exec_failok(command): def exec_failexit(command): + """Helper function to call a command with error and exit if failing.""" print_running(command) output = commands.getstatusoutput(command) retcode = output[0] @@ -179,9 +94,13 @@ def exec_failexit(command): print "" def yum(command, pkgs = ""): + """Helper function to call a yum command on a list of packages.""" exec_failexit("/usr/bin/yum -y %s %s" % (command, pkgs)) def check_migration_version(): + """ + Verify that the command 'subscription-manager-migration' isn't too old. + """ required_version = rpmUtils.miscutils.stringToVersion('1.14.2') err = "subscription-manager-migration not found" @@ -199,6 +118,9 @@ def check_migration_version(): def install_prereqs(): + """ + Install subscription manager and its prerequisites. + """ print_generic("Installing subscription manager prerequisites") yum("remove", "subscription-manager-gnome") yum("install", "subscription-manager subscription-manager-migration-*") @@ -206,6 +128,10 @@ def install_prereqs(): def get_bootstrap_rpm(): + """ + Retrieve Client CA Certificate RPMs from the Satellite 6 server. + If called with --force, calls clean_katello_agent(). + """ if options.force: clean_katello_agent() print_generic("Retrieving Client CA Certificate RPMs") @@ -213,6 +139,15 @@ def get_bootstrap_rpm(): def migrate_systems(org_name, activationkey): + """ + Call `rhn-migrate-classic-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 + (TODO why?). + If called with "--legacy-purge", uses "legacy-user" and "legacy-password" + to remove the machine. + Option "--force" is given further. + """ if options.no_foreman: org_label = org_name else: @@ -230,6 +165,11 @@ def migrate_systems(org_name, activationkey): def register_systems(org_name, activationkey, release): + """ + Register the host to Satellite 6's organization using + `subscription-manager` and the given activation key. + Option "--force" is given further. + """ if options.no_foreman: org_label = org_name else: @@ -242,16 +182,19 @@ def register_systems(org_name, activationkey, release): def unregister_system(): + """Unregister the host using `subscription-manager`.""" print_generic("Unregistering") exec_failexit("/usr/sbin/subscription-manager unregister") def clean_katello_agent(): + """Remove old Katello agent (aka Gofer) and certificate RPMs.""" print_generic("Removing old Katello agent and certs") yum("erase", "katello-ca-consumer-* katello-agent gofer") def install_katello_agent(): + """Install Katello agent (aka Gofer) and activate /start it.""" print_generic("Installing the Katello agent") yum("install", "katello-agent") exec_failexit("/sbin/chkconfig goferd on") @@ -259,12 +202,14 @@ def install_katello_agent(): def clean_puppet(): + """Remove old Puppet Agent and its configuration""" print_generic("Cleaning old Puppet Agent") yum("erase", "puppet") exec_failexit("rm -rf /var/lib/puppet/") def clean_environment(): + """Undefine `LD_LIBRARY_PATH` and `LD_PRELOAD` (TODO why?).""" for key in ['LD_LIBRARY_PATH', 'LD_PRELOAD']: os.environ.pop(key, None) @@ -275,6 +220,7 @@ def clean_environment(): katellofacts.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") yum("install", "puppet") @@ -306,11 +252,13 @@ def install_puppet_agent(): def remove_obsolete_packages(): + """Remove old RHN packages""" print_generic("Removing old RHN packages") yum("remove", "rhn-setup rhn-client-tools yum-rhn-plugin rhnsd rhn-check rhnlib spacewalk-abrt spacewalk-oscap osad rh-*-rhui-client") def fully_update_the_box(): + """Call `yum -y update` to upgrade the host.""" print_generic("Fully Updating The Box") yum("update") @@ -318,6 +266,10 @@ def fully_update_the_box(): # curl https://satellite.example.com:9090/ssh/pubkey >> ~/.ssh/authorized_keys # sort -u ~/.ssh/authorized_keys def install_foreman_ssh_key(): + """ + Download and install the Satellite's SSH public key into the foreman user's + authorized keys file, so that remote execution becomes possible. + """ userpw = pwd.getpwnam(options.remote_exec_user) foreman_ssh_dir = os.sep.join([userpw.pw_dir,'.ssh']) foreman_ssh_authfile = os.sep.join([foreman_ssh_dir,'authorized_keys']) @@ -341,19 +293,23 @@ def install_foreman_ssh_key(): print_generic("Foreman's SSH key was added to %s" % foreman_ssh_authfile) -# a substitute/supplement to urllib2.HTTPErrorProcessor -# that doesn't raise exceptions on status codes 201,204,206 class BetterHTTPErrorProcessor(urllib2.BaseHandler): + """ + A substitute/supplement class to urllib2.HTTPErrorProcessor + that doesn't raise exceptions on status codes 201,204,206 + """ def http_error_201(self, request, response, code, msg, hdrs): return response def http_error_204(self, request, response, code, msg, hdrs): return response def http_error_206(self, request, response, code, msg, hdrs): return response -opener = urllib2.build_opener(BetterHTTPErrorProcessor) -urllib2.install_opener(opener) def call_api(url, data=None, method='GET'): + """ + Helper function to place an API call returning JSON results and doing + some error handling. Any error results in an exit. + """ try: request = urllib2.Request(url) if options.verbose: @@ -391,34 +347,47 @@ def call_api(url, data=None, method='GET'): def get_json(url): + """Use `call_api` to place a "GET" REST API call.""" return call_api(url) def post_json(url, jdata): + """Use `call_api` to place a "POST" REST API call.""" return call_api(url, data=jdata, method='POST') def delete_json(url): + """Use `call_api` to place a "DELETE" REST API call.""" return call_api(url, method='DELETE') def put_json(url): + """Use `call_api` to place a "PUT" REST API call.""" return call_api(url, method='PUT') def return_matching_foreman_key(api_name, search_key, return_key, null_result_ok=False): + """ + Function uses `return_matching_key` to make an API call to Foreman. + """ return return_matching_key("/api/v2/" + api_name, search_key, return_key, null_result_ok) def return_matching_katello_key(api_name, search_key, return_key, null_result_ok=False): + """ + Function uses `return_matching_key` to make an API call to Katello. + """ return return_matching_key("/katello/api/" + api_name, search_key, return_key, null_result_ok) -# Search in API -# given a search key, return the ID -# api_path is the path in url for API name, search_key must contain also the key for search (name=, title=, ...) -# the search_key must be quoted in advance def return_matching_key(api_path, search_key, return_key, null_result_ok=False): + """ + Search in API given a search key, which must be unique, then returns the + field given in "return_key" as ID. + api_path is the path in url for API name, search_key must contain also + 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))]) return_values = get_json(myurl) result_len = len(return_values['results']) @@ -433,6 +402,11 @@ def return_matching_key(api_path, search_key, return_key, null_result_ok=False): def return_puppetenv_for_hg(hg_id): + """ + Return the Puppet environment of the given hostgroup ID, either directly + 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) hostgroup = get_json(myurl) if hostgroup['environment_name']: @@ -444,6 +418,10 @@ def return_puppetenv_for_hg(hg_id): return 'production' 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" domid = return_matching_foreman_key('domains', 'name="%s"' % domain, 'id', True) if not domid: @@ -452,6 +430,10 @@ def create_domain(domain, orgid, locid): post_json(myurl, jsondata) def create_host(): + """ + Call Foreman API to create a host entry associated with the + host group, organization & location, domain and architecture. + """ myhgid = return_matching_foreman_key('hostgroups', 'title="%s"' % options.hostgroup, 'id', False) if options.location: mylocid = return_matching_foreman_key('locations', 'title="%s"' % options.location, 'id', False) @@ -497,18 +479,23 @@ 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/" print_running("Deleting host id %s for host %s" % (host_id, FQDN)) delete_json("%s/%s" % (myurl, host_id)) 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" print_running("Disassociating host id %s for host %s" % (host_id, FQDN)) put_json(myurl) def check_rhn_registration(): + """Helper function to check if host is registered to legacy RHN.""" if os.path.exists('/etc/sysconfig/rhn/systemid'): retcode = commands.getstatusoutput('rhn-channel -l')[0] return retcode == 0 @@ -516,11 +503,13 @@ def check_rhn_registration(): 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 get_api_port(): + """Helper function to get the server port from Subscription Manager.""" configparser = SafeConfigParser() configparser.read('/etc/rhsm/rhsm.conf') return configparser.get('server', 'port') @@ -530,6 +519,7 @@ def get_api_port(): def prepare_rhel5_migration(): + """Execute specific preparations steps for RHEL 5 (TODO why?).""" install_prereqs() # only do the certificate magic if 69.pem is not present @@ -570,79 +560,207 @@ class MEOptions: if os.path.exists('/etc/sysconfig/rhn/systemid'): os.remove('/etc/sysconfig/rhn/systemid') -# Add check for root user. Done here to allow an unprivileged user to run the script -# to see its various options. -if os.getuid() != 0: - print_error("This script requires root-level access") - sys.exit(1) - -# try to import json or simplejson -# do it at this point in the code to have our custom print and exec functions available -try: - import json -except ImportError: +if __name__ == '__main__': + + ## Register our better HTTP processor as default opener for URLs. + opener = urllib2.build_opener(BetterHTTPErrorProcessor) + urllib2.install_opener(opener) + + ## Gather FQDN, HOSTNAME and DOMAIN. + FQDN = socket.getfqdn() + if FQDN.find(".") != -1: + HOSTNAME = FQDN.split('.')[0] + DOMAIN = FQDN[FQDN.index('.')+1:] + else: + HOSTNAME = FQDN + DOMAIN = None + + ## Gather MAC Address. + MAC = None try: - import simplejson as json + 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 not MAC: + MAC = "00:00:00:00:00:00" + + ## Gather API port (HTTPS), ARCHITECTURE and (OS) RELEASE + API_PORT = "443" + ARCHITECTURE = get_architecture() + try: + RELEASE = platform.linux_distribution()[1] + except AttributeError: + RELEASE = platform.dist()[1] + + ## Define and parse the options + parser = OptionParser() + parser.add_option("-s", "--server", dest="foreman_fqdn", help="FQDN of Foreman OR Capsule - omit https://", metavar="foreman_fqdn") + parser.add_option("-l", "--login", dest="login", default='admin', help="Login user for API Calls", metavar="LOGIN") + parser.add_option("-p", "--password", dest="password", help="Password for specified user. Will prompt if omitted", metavar="PASSWORD") + parser.add_option("--legacy-login", dest="legacy_login", default='admin', help="Login user for Satellite 5 API Calls", metavar="LOGIN") + parser.add_option("--legacy-password", dest="legacy_password", help="Password for specified Satellite 5 user. Will prompt if omitted", metavar="PASSWORD") + parser.add_option("--legacy-purge", dest="legacy_purge", action="store_true", help="Purge system from the Legacy environment (e.g. Sat5)") + parser.add_option("-a", "--activationkey", dest="activationkey", help="Activation Key to register the system", metavar="ACTIVATIONKEY") + parser.add_option("-P", "--skip-puppet", dest="no_puppet", action="store_true", default=False, help="Do not install Puppet") + parser.add_option("--skip-foreman", dest="no_foreman", action="store_true", default=False, help="Do not create a Foreman host. Implies --skip-puppet. When using --skip-foreman, you MUST pass the Organization's LABEL, not NAME") + parser.add_option("-g", "--hostgroup", dest="hostgroup", help="Title of the Hostgroup in Foreman that the host is to be associated with", metavar="HOSTGROUP") + parser.add_option("-L", "--location", dest="location", help="Title of the Location in Foreman that the host is to be associated with", metavar="LOCATION") + parser.add_option("-O", "--operatingsystem", dest="operatingsystem", default=None, help="Title of the Operating System in Foreman that the host is to be associated with", metavar="OPERATINGSYSTEM") + parser.add_option("--partitiontable", dest="partitiontable", default=None, help="Name of the Partition Table in Foreman that the host is to be associated with", metavar="PARTITIONTABLE") + parser.add_option("-o", "--organization", dest="org", default='Default Organization', help="Name of the Organization in Foreman that the host is to be associated with", metavar="ORG") + parser.add_option("-S", "--subscription-manager-args", dest="smargs", default="", help="Which additional arguments shall be passed to subscription-manager", metavar="ARGS") + parser.add_option("--rhn-migrate-args", dest="rhsmargs", default="", help="Which additional arguments shall be passed to rhn-migrate-classic-to-rhsm", metavar="ARGS") + parser.add_option("-u", "--update", dest="update", action="store_true", help="Fully Updates the System") + parser.add_option("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") + parser.add_option("-f", "--force", dest="force", action="store_true", help="Force registration (will erase old katello and puppet certs)") + parser.add_option("--add-domain", dest="add_domain", action="store_true", help="Automatically add the clients domain to Foreman") + parser.add_option("--remove", dest="remove", action="store_true", help="Instead of registring the machine to Foreman remove it") + parser.add_option("-r", "--release", dest="release", default=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("--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) + parser.add_option("--rex-user", dest="remote_exec_user", default="root", help="Local user used by Foreman's remote execution feature.") + parser.add_option("--enablerepos", dest="enablerepos", help="Repositories to be enabled via subscription-manager - comma separated", metavar="enablerepos") + (options, args) = parser.parse_args() + + ## Validate that the options make sense or exit with a message. + if not (options.foreman_fqdn and options.login and (options.remove or (options.org and options.activationkey and (options.no_foreman or options.hostgroup)))): + print "Must specify server, login, organization, hostgroup, and activation key. See usage:" + parser.print_help() + print "\nExample usage: ./bootstrap.py -l admin -s foreman.example.com -o 'Default Organization' -L 'Default Location' -g My_Hostgroup -a My_Activation_Key" + sys.exit(1) + + ## Exit if DOMAIN isn't set and Puppet must be installed (without force) + if not DOMAIN and not (options.force or options.no_puppet): + print "We could not determine the domain of this machine, most probably `hostname -f` does not return the FQDN." + print "This can lead to Puppet missbehaviour and thus the script will terminate now." + print "You can override this by passing --force or --skip-puppet" + sys.exit(1) + + ## Ask for the password if not given as option + if not options.password and not options.no_foreman: + options.password = getpass.getpass("%s's password:" % options.login) + + ## Puppet won't be installed if Foreman Host shall not be created + if options.no_foreman: + options.no_puppet = True + + ## Output all parameters if verbose. + if options.verbose: + print "HOSTNAME - %s" % HOSTNAME + print "DOMAIN - %s" % DOMAIN + print "RELEASE - %s" % RELEASE + print "MAC - %s" % MAC + print "foreman_fqdn - %s" % options.foreman_fqdn + print "LOGIN - %s" % options.login + print "PASSWORD - %s" % options.password + print "HOSTGROUP - %s" % options.hostgroup + print "LOCATION - %s" % options.location + print "OPERATINGSYSTEM - %s" % options.operatingsystem + print "PARTITIONTABLE - %s" % options.partitiontable + print "ORG - %s" % options.org + print "ACTIVATIONKEY - %s" % options.activationkey + print "UPDATE - %s" % options.update + + + ## Exit if the user isn't root. + # Done here to allow an unprivileged user to run the script to see + # its various options. + if os.getuid() != 0: + print_error("This script requires root-level access") + sys.exit(1) + + ## Try to import json or simplejson. + # do it at this point in the code to have our custom print and exec + # functions available + try: + import json except ImportError: - print_warning("Could neither import json nor simplejson, will try to install simplejson and re-import") - yum("install", "python-simplejson") try: import simplejson as json except ImportError: - print_error("Could not install python-simplejson") - sys.exit(1) - - -clean_environment() - -if not options.remove and int(RELEASE[0]) == 5: - prepare_rhel5_migration() - -if options.remove: - API_PORT = get_api_port() - unregister_system() - if not options.no_foreman: - host_id = return_matching_foreman_key('hosts', 'name="%s"' % FQDN, 'id', True) - if host_id is not None: - disassociate_host(host_id) - delete_host(host_id) - clean_katello_agent() - if not options.no_puppet: - clean_puppet() -elif check_rhn_registration(): - print_generic('This system is registered to RHN. Attempting to migrate via rhn-classic-migrate-to-rhsm') - install_prereqs() - check_migration_version() - get_bootstrap_rpm() - API_PORT = get_api_port() - if not options.no_foreman: - create_host() - migrate_systems(options.org, options.activationkey) - if options.enablerepos: - enable_repos() -else: - print_generic('This system is not registered to RHN. Attempting to register via subscription-manager') - get_bootstrap_rpm() - API_PORT = get_api_port() - if not options.no_foreman: - create_host() - register_systems(options.org, options.activationkey, options.release) - if options.enablerepos: - enable_repos() - -if not options.remove: - install_katello_agent() - if options.update: - fully_update_the_box() - - if not options.no_puppet: - if options.force: + print_warning("Could neither import json nor simplejson, will try to install simplejson and re-import") + yum("install", "python-simplejson") + try: + import simplejson as json + except ImportError: + print_error("Could not install python-simplejson") + sys.exit(1) + + ## Clean the environment from LD_... variables + clean_environment() + + ## IF RHEL 5 and not removing, prepare the migration. + if not options.remove and int(RELEASE[0]) == 5: + prepare_rhel5_migration() + + if options.remove: + ## IF remove, disassociate/delete host, unregister, + ## uninstall katello and optionally puppet agents + API_PORT = get_api_port() + unregister_system() + if not options.no_foreman: + host_id = return_matching_foreman_key('hosts', 'name="%s"' % FQDN, 'id', True) + if host_id is not None: + disassociate_host(host_id) + delete_host(host_id) + clean_katello_agent() + if not options.no_puppet: clean_puppet() - install_puppet_agent() - - if options.removepkgs: - remove_obsolete_packages() - - if options.remote_exec: - install_foreman_ssh_key() + elif check_rhn_registration(): + ## ELIF registered to RHN, install subscription-manager prerequs + ## get CA RPM, optionally create host, + ## migrate via rhn-classic-migrate-to-rhsm + print_generic('This system is registered to RHN. Attempting to migrate via rhn-classic-migrate-to-rhsm') + install_prereqs() + check_migration_version() + get_bootstrap_rpm() + API_PORT = get_api_port() + if not options.no_foreman: + create_host() + migrate_systems(options.org, options.activationkey) + if options.enablerepos: + enable_repos() + else: + ## ELSE get CA RPM, optionally create host, + ## register via subscription-manager + print_generic('This system is not registered to RHN. Attempting to register via subscription-manager') + get_bootstrap_rpm() + API_PORT = get_api_port() + if not options.no_foreman: + create_host() + register_systems(options.org, options.activationkey, options.release) + if options.enablerepos: + enable_repos() + + if not options.remove: + ## IF not removing, install Katello agent, optionally update host, + ## optionally clean and install Puppet agent + ## optionally remove legacy RHN packages + install_katello_agent() + if options.update: + fully_update_the_box() + + if not options.no_puppet: + if options.force: + clean_puppet() + install_puppet_agent() + + if options.removepkgs: + remove_obsolete_packages() + + if options.remote_exec: + install_foreman_ssh_key()