#!/usr/bin/python
#
# Copyright (c) 2010 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#

import getpass
import gettext
import libxml2
import logging
import os
import re
import shutil
import subprocess
import sys

import rhsm.config

from datetime import datetime
from M2Crypto.SSL import SSLError

from rhn import rpclib

from rhsm.connection import RemoteServerException, RestlibException
from rhsm.utils import ServerUrlParseError

_ = gettext.gettext

_LIBPATH = "/usr/share/rhsm"
if _LIBPATH not in sys.path:
    sys.path.append(_LIBPATH)

from subscription_manager import injection as inj
from subscription_manager.cli import system_exit
from subscription_manager.i18n_optparse import OptionParser, \
        USAGE, WrappedIndentedHelpFormatter
from subscription_manager.productid import ProductDatabase
from subscription_manager import repolib
from rhsm.utils import parse_url
from rhsm import ourjson as json

_RHNLIBPATH = "/usr/share/rhn"
if _RHNLIBPATH not in sys.path:
    sys.path.append(_RHNLIBPATH)

# Don't raise ImportErrors so we can run the unit tests on Fedora.
try:
    from up2date_client.config import initUp2dateConfig
except ImportError:
    def initUp2dateConfig():
        raise NotImplementedError(_("Could not find up2date_client.config module!"))

try:
    from up2date_client.rhnChannel import getChannels
except ImportError:
    def getChannels():
        raise NotImplementedError(_("Could not find up2date_client.rhnChannel module!"))

log = logging.getLogger('rhsm-app.' + __name__)

SEE_LOG_FILE = _(u"See /var/log/rhsm/rhsm.log for more details.")

CONNECTION_FAILURE = _(u"Unable to connect to certificate server: %s.  ") + SEE_LOG_FILE

FACT_FILE = "/etc/rhsm/facts/migration.facts"

YUM_PLUGIN_CONF = '/etc/yum/pluginconf.d/rhnplugin.conf'

DOUBLE_MAPPED = "rhel-.*?-(client|server)-dts-(5|6)-beta(-debuginfo)?"
# The (?!-beta) bit is a negative lookahead assertion.  So we won't match
# if the 5 or 6 is followed by the word "-beta"
SINGLE_MAPPED = "rhel-.*?-(client|server)-dts-(5|6)(?!-beta)(-debuginfo)?"


class InvalidChoiceError(Exception):
    pass


class Menu(object):
    def __init__(self, choices, header):
        # choices is a tuple with the first value being the display string
        # and the second value being the value to return.
        self.choices = choices
        self.header = header

    def choose(self):
        while True:
            self.display()
            selection = raw_input("? ").strip()
            try:
                return self._get_item(selection)
            except InvalidChoiceError:
                self.display_invalid()

    def display(self):
        print self.header
        for index, entry in enumerate(self.choices):
            print "%s. %s" % (index + 1, entry[0])

    def display_invalid(self):
        print _("You have entered an invalid choice.  Enter a choice from the menu above.")

    def _get_item(self, selection):
        try:
            index = int(selection) - 1
            # In case some joker enters zero or a negative number
            if index < 0:
                raise InvalidChoiceError
        except TypeError:
            raise InvalidChoiceError
        except ValueError:
            raise InvalidChoiceError

        try:
            return self.choices[index][1]
        except IndexError:
            raise InvalidChoiceError


class UserCredentials(object):
    def __init__(self, username, password):
        self.username = username
        self.password = password


class MigrationEngine(object):
    def __init__(self, options):
        self.rhncfg = initUp2dateConfig()
        self.rhsmcfg = rhsm.config.initConfig()

        self.proxy_host = None
        self.proxy_port = None
        self.proxy_user = None
        self.proxy_pass = None

        self.cp = None
        self.db = ProductDatabase()

        self.consumer_id = None

        self.options = options
        self.is_hosted = is_hosted()

    def authenticate(self, username, password, user_prompt, pw_prompt):
        if not username:
            username = raw_input(user_prompt).strip()

        if not password:
            password = getpass.getpass(prompt=pw_prompt)

        return UserCredentials(username, password)

    def get_auth(self):
        self.legacy_creds = self.authenticate(self.options.legacy_user, self.options.legacy_password,
                _("Legacy username: "), _("Legacy password: "))

        if not self.is_hosted or self.options.destination_url:
            self.destination_creds = self.authenticate(self.options.destination_user, self.options.destination_password,
                    _("Destination username: "), _("Destination password: "))
        else:
            self.destination_creds = self.legacy_creds   # make them the same

    def transfer_http_proxy_settings(self):
        if self.rhncfg['enableProxy']:
            http_proxy = self.rhncfg['httpProxy']
            if http_proxy[:7] == "http://":
                http_proxy = http_proxy[7:]
            try:
                self.proxy_host, self.proxy_port = http_proxy.split(':')
            except ValueError, e:
                log.exception(e)
                system_exit(1, _("Could not read legacy proxy settings.  ") + SEE_LOG_FILE)

            if self.rhncfg['enableProxyAuth']:
                self.proxy_user = self.rhncfg['proxyUser']
                self.proxy_pass = self.rhncfg['proxyPassword']

            log.info("Using proxy %s:%s" % (self.proxy_host, self.proxy_port))
            if self.options.noproxy:
                # If the user doesn't want to use a proxy to connect to their subscription
                # management server, then remove any proxy information that may have crept in.
                self.rhsmcfg.set('server', 'proxy_hostname', '')
                self.rhsmcfg.set('server', 'proxy_port', '')
                self.rhsmcfg.set('server', 'proxy_user', '')
                self.rhsmcfg.set('server', 'proxy_password', '')
            else:
                self.rhsmcfg.set('server', 'proxy_hostname', self.proxy_host)
                self.rhsmcfg.set('server', 'proxy_port', self.proxy_port)
                self.rhsmcfg.set('server', 'proxy_user', self.proxy_user or '')
                self.rhsmcfg.set('server', 'proxy_password', self.proxy_pass or '')
            self.rhsmcfg.save()

    def _get_connection_info(self):
        try:
            if self.options.destination_url is None:
                hostname = self.rhsmcfg.get('server', 'hostname')
                port = self.rhsmcfg.get_int('server', 'port')
                prefix = self.rhsmcfg.get('server', 'prefix')
            else:
                (_user, _password, hostname, port, prefix) = parse_url(self.options.destination_url, default_port=443)
        except ServerUrlParseError, e:
            system_exit(-1, _("Error parsing server URL: %s") % e.msg)

        connection_info = {'host': hostname, 'ssl_port': int(port), 'handler': prefix}

        if not self.options.noproxy:
            connection_info['proxy_hostname_arg'] = self.proxy_host
            connection_info['proxy_port_arg'] = self.proxy_port and int(self.proxy_port)
            connection_info['proxy_user_arg'] = self.proxy_user
            connection_info['proxy_password_arg'] = self.proxy_pass

        return connection_info

    def get_candlepin_basic_auth_connection(self, username, password):
        self.cp_provider = inj.require(inj.CP_PROVIDER)

        connection_info = self._get_connection_info()

        self.cp_provider.set_user_pass(username, password)
        self.cp_provider.set_connection_info(**connection_info)

        return self.cp_provider.get_basic_auth_cp()

    def check_ok_to_proceed(self, username):
        # check if this machine is already registered to Certicate-based RHN
        identity = inj.require(inj.IDENTITY)
        if identity.is_valid():
            if self.options.five_to_six:
                msgs = [_("This system appears to already be registered to Satellite 6.")]
            else:
                msgs = [_("This system appears to already be registered to Red Hat Subscription Management.")]
                msgs.append(_("Please visit https://access.redhat.com/management/consumers/%s to view the profile details.") % identity.uuid)
            system_exit(1, msgs)

        try:
            self.cp.getOwnerList(username)
        except SSLError, e:
            print _("The CA certificate for the destination server has not been installed.")
            system_exit(1, CONNECTION_FAILURE % e)
        except Exception, e:
            log.exception(e)
            system_exit(1, CONNECTION_FAILURE % e)

    def get_org(self, username):
        try:
            owner_list = self.cp.getOwnerList(username)
        except Exception, e:
            log.exception(e)
            system_exit(1, CONNECTION_FAILURE % e)

        if len(owner_list) == 0:
            system_exit(1, _("%s cannot register with any organizations.") % username)
        else:
            if self.options.org:
                org_input = self.options.org
            elif len(owner_list) == 1:
                org_input = owner_list[0]['key']
            else:
                org_input = raw_input(_("Org: ")).strip()

            org = None
            for owner_data in owner_list:
                if owner_data['key'] == org_input or owner_data['displayName'] == org_input:
                    org = owner_data['key']
                    break
            if not org:
                system_exit(1, _("Couldn't find organization '%s'.") % org_input)
        return org

    def get_environment(self, owner_key):
        environment_list = []
        try:
            if self.cp.supports_resource('environments'):
                environment_list = self.cp.getEnvironmentList(owner_key)
            elif self.options.environment:
                system_exit(1, _("Environments are not supported by this server."))
        except Exception, e:
            log.exception(e)
            system_exit(1, CONNECTION_FAILURE % e)

        environment = None
        if len(environment_list) > 0:
            if self.options.environment:
                env_input = self.options.environment
            elif len(environment_list) == 1:
                env_input = environment_list[0]['name']
            else:
                env_input = raw_input(_("Environment: ")).strip()

            for env_data in environment_list:
                # See BZ #978001
                if (env_data['name'] == env_input or
                   ('label' in env_data and env_data['label'] == env_input) or
                   ('displayName' in env_data and env_data['displayName'] == env_input)):
                    environment = env_data['name']
                    break
            if not environment:
                system_exit(1, _("Couldn't find environment '%s'.") % env_input)

        return environment

    def connect_to_rhn(self, credentials):
        hostname = self.rhncfg['serverURL'].split('/')[2]
        server_url = 'https://%s/rpc/api' % (hostname)
        try:
            if self.rhncfg['enableProxy']:
                proxy = "%s:%s" % (self.proxy_host, self.proxy_port)
                log.info("Using proxy %s for legacy API methods" % (proxy))
                if self.rhncfg['enableProxyAuth']:
                    proxy = "@".join(["%s:%s" % (self.proxy_user, self.proxy_pass), proxy])
            else:
                proxy = None

            rpc_session = rpclib.Server(server_url, proxy=proxy)

            ca = self.rhncfg["sslCACert"]
            rpc_session.add_trusted_cert(ca)

            session_key = rpc_session.auth.login(credentials.username, credentials.password)
            return (rpc_session, session_key)
        except Exception, e:
            log.exception(e)
            system_exit(1, _("Unable to authenticate to legacy server.  ") + SEE_LOG_FILE)

    def check_is_org_admin(self, rpc_session, session_key, username):
        try:
            roles = rpc_session.user.listRoles(session_key, username)
        except Exception, e:
            log.exception(e)
            system_exit(1, _("Problem encountered determining user roles on legacy server.  ") + SEE_LOG_FILE)
        if "org_admin" not in roles:
            system_exit(1, _("You must be an org admin to successfully run this script."))

    def resolve_base_channel(self, label, rpc_session, session_key):
        try:
            details = rpc_session.channel.software.getDetails(session_key, label)
        except Exception, e:
            log.exception(e)
            system_exit(1, _("Problem encountered getting the list of subscribed channels.  ") + SEE_LOG_FILE)
        if details['clone_original']:
            return self.resolve_base_channel(details['clone_original'], rpc_session, session_key)
        return details

    def get_subscribed_channels_list(self, rpc_session, session_key):
        try:
            channels = getChannels().channels()
        except Exception, e:
            log.exception(e)
            system_exit(1, _("Problem encountered getting the list of subscribed channels.  ") + SEE_LOG_FILE)
        if self.options.five_to_six:
            channels = [self.resolve_base_channel(c['label'], rpc_session, session_key) for c in channels]
        return [x['label'] for x in channels]

    def print_banner(self, msg):
        print "\n+-----------------------------------------------------+"
        print msg
        print "+-----------------------------------------------------+"

    def check_for_conflicting_channels(self, subscribed_channels):
        jboss_channel = False
        for channel in subscribed_channels:
            if channel.startswith("jbappplatform"):
                if jboss_channel:
                    system_exit(1, _("You are subscribed to more than one jbappplatform channel."
                                    "  This script does not support that configuration."))
                jboss_channel = True

    def get_release(self):
        f = open('/etc/redhat-release')
        lines = f.readlines()
        f.close()
        release = "RHEL-" + str(lines).split(' ')[6].split('.')[0]
        return release

    def read_channel_cert_mapping(self, mappingfile):
        f = open(mappingfile)
        lines = f.readlines()
        dic_data = {}
        for line in lines:
            # Lines should be of the format
            # key: value
            # Lines beginning with non-letters will be ignored
            if re.match("^[a-zA-Z]", line):
                line = line.replace("\n", "")
                key, val = line.strip().split(": ")
                dic_data[key] = val
        return dic_data

    def handle_collisions(self, applicable_certs):
        # if we have the same product IDs mapping to multiple certificates, we must abort.
        collisions = dict((prod_id, mappings) for prod_id, mappings in applicable_certs.items() if len(mappings) > 1)
        if not collisions:
            return

        log.error("Aborting. Detected the following product ID collisions: %s", collisions)
        self.print_banner(_("Unable to continue migration!"))
        print _("You are subscribed to channels that have conflicting product certificates.")
        for prod_id, mappings in collisions.items():
            # Flatten the list of lists
            colliding_channels = [item for sublist in mappings.values() for item in sublist]
            print _("The following channels map to product ID %s:") % prod_id
            for c in sorted(colliding_channels):
                print "\t%s" % c
        print _("Reduce the number of channels per product ID to 1 and run migration again.")
        print _("To remove a channel, use 'rhn-channel --remove --channel=<conflicting_channel>'.")
        sys.exit(1)

    def deploy_prod_certificates(self, subscribed_channels):
        release = self.get_release()
        mappingfile = "/usr/share/rhsm/product/" + release + "/channel-cert-mapping.txt"
        log.info("Using mapping file %s", mappingfile)

        try:
            dic_data = self.read_channel_cert_mapping(mappingfile)
        except IOError, e:
            log.exception(e)
            system_exit(1, _("Unable to read mapping file: %(mappingfile)s.\n"
                "Please check that you have the %(package)s package installed.") % {
                    "mappingfile": mappingfile,
                    "package": "subscription-manager-migration-data"})

        applicable_certs = {}
        valid_rhsm_channels = []
        invalid_rhsm_channels = []
        unrecognized_channels = []

        for channel in subscribed_channels:
            try:
                if dic_data[channel] != 'none':
                    valid_rhsm_channels.append(channel)
                    cert = dic_data[channel]
                    log.info("Mapping found for: %s = %s", channel, cert)
                    prod_id = cert.split('-')[-1].split('.pem')[0]
                    cert_to_channels = applicable_certs.setdefault(prod_id, {})
                    cert_to_channels.setdefault(cert, []).append(channel)
                else:
                    invalid_rhsm_channels.append(channel)
                    log.info("%s is not mapped to any certificates", channel)
            except Exception:
                unrecognized_channels.append(channel)

        if invalid_rhsm_channels:
            self.print_banner(_("Channels not available on %s:") % self.options.destination_url)
            for i in invalid_rhsm_channels:
                print i

        if unrecognized_channels:
            self.print_banner(_("No product certificates are mapped to these legacy channels:"))
            for i in unrecognized_channels:
                print i

        if unrecognized_channels or invalid_rhsm_channels:
            if not self.options.force:
                print(_("\nUse --force to ignore these channels and continue the migration.\n"))
                sys.exit(1)

        # At this point applicable_certs looks something like this
        # { '1': { 'cert-a-1.pem': ['channel1', 'channel2'], 'cert-b-1.pem': ['channel3'] } }
        # This is telling us that product ID 1 maps to two certificates, cert-a and cert-b.
        # Two channels map to the cert-a certificate and one channel maps to cert-b.
        # If we wind up in a situation where a user has channels that map to two different
        # certificates with the same product ID, (e.g. len(hash[product_id]) > 1) we've got a
        # collision and must abort.
        self.handle_collisions(applicable_certs)

        log.info("Certs to be installed: %s", applicable_certs)

        self.print_banner(_("Installing product certificates for these legacy channels:"))
        for i in valid_rhsm_channels:
            print i

        release = self.get_release()

        # creates the product directory if it doesn't already exist
        product_dir = inj.require(inj.PROD_DIR)
        db_modified = False
        for cert_to_channels in applicable_certs.values():
            # At this point handle_collisions should have verified that len(cert_to_channels) == 1
            cert, channels = cert_to_channels.items()[0]
            source_path = os.path.join("/usr/share/rhsm/product", release, cert)
            truncated_cert_name = cert.split('-')[-1]
            destination_path = os.path.join(product_dir.path, truncated_cert_name)
            log.info("Copying %s to %s ", source_path, destination_path)
            shutil.copy2(source_path, destination_path)

            # See BZ #972883. Add an entry to the repo db telling subscription-manager
            # that packages for this product were installed by the RHN repo which
            # conveniently uses the channel name for the repo name.
            db_id = truncated_cert_name.split('.pem')[0]
            for chan in channels:
                self.db.add(db_id, chan)
                db_modified = True

        if db_modified:
            self.db.write()
        print _("\nProduct certificates installed successfully to %s.") % product_dir.path

    def clean_up(self, subscribed_channels):
        # Hack to address BZ 853233
        product_dir = inj.require(inj.PROD_DIR)
        if os.path.isfile(os.path.join(product_dir.path, "68.pem")) and \
            os.path.isfile(os.path.join(product_dir.path, "71.pem")):
            try:
                os.remove(os.path.join(product_dir.path, "68.pem"))
                self.db.delete("68")
                self.db.write()
                log.info("Removed 68.pem due to existence of 71.pem")
            except OSError, e:
                log.info(e)

        # Hack to address double mapping for 180.pem and 17{6|8}.pem
        is_double_mapped = [x for x in subscribed_channels if re.match(DOUBLE_MAPPED, x)]
        is_single_mapped = [x for x in subscribed_channels if re.match(SINGLE_MAPPED, x)]

        if is_double_mapped and is_single_mapped:
            try:
                os.remove(os.path.join(product_dir.path, "180.pem"))
                self.db.delete("180")
                self.db.write()
                log.info("Removed 180.pem")
            except OSError, e:
                log.info(e)

    def get_system_id(self, system_id_path):
        p = libxml2.parseDoc(open(system_id_path, 'r').read())
        system_id = int(p.xpathEval('string(//member[* = "system_id"]/value/string)').split('-')[1])
        return system_id

    def write_migration_facts(self):
        migration_date = datetime.now().isoformat()

        if not os.path.exists(FACT_FILE):
            f = open(FACT_FILE, 'w')
            json.dump({"migration.classic_system_id": self.get_system_id(self.rhncfg["systemIdPath"]),
                       "migration.migrated_from": self.rhncfg['serverURL'],
                       "migration.migration_date": migration_date}, f)
            f.close()

    def disable_yum_rhn_plugin(self):
        # 'Inspired by' up2date_client/rhnreg.py
        """
        Disable yum-rhn-plugin by setting enabled=0 in file
        /etc/yum/pluginconf.d/rhnplugin.conf
        Can thrown IOError exception.
        """
        log.info("Disabling rhnplugin.conf")
        f = open(YUM_PLUGIN_CONF, 'r')
        lines = f.readlines()
        f.close()
        main_section = False
        f = open(YUM_PLUGIN_CONF, 'w')
        for line in lines:
            if re.match("^\[.*]", line):
                if re.match("^\[main]", line):
                    main_section = True
                else:
                    main_section = False
            if main_section:
                line = re.sub('^(\s*)enabled\s*=.+', r'\1enabled = 0', line)
            f.write(line)
        f.close()

    def legacy_unentitle(self, rpc_session):
        system_id = open(self.rhncfg["systemIdPath"], 'r').read()
        try:
            rpc_session.system.unentitle(system_id)
        except Exception, e:
            log.exception("Could not unentitle system on Satellite 5.", e)
            system_exit(1, _("Could not unentitle system on legacy server.  ") + SEE_LOG_FILE)
        try:
            self.disable_yum_rhn_plugin()
        except Exception:
            pass

    def legacy_purge(self, rpc_session, session_key):
        system_id_path = self.rhncfg["systemIdPath"]
        system_id = self.get_system_id(system_id_path)

        log.info("Deleting system %s from legacy server...", system_id)
        try:
            result = rpc_session.system.deleteSystems(session_key, system_id)
        except Exception:
            log.exception("Could not delete system %s from legacy server" % system_id)
            # If we time out or get a network error, log it and keep going.
            shutil.move(system_id_path, system_id_path + ".save")
            print _("Did not receive a completed unregistration message from legacy server for system %s.") % system_id

            if self.is_hosted:
                print _("Please investigate on the Customer Portal at https://access.redhat.com.")
            return

        if result:
            log.info("System %s deleted.  Removing systemid file and disabling rhnplugin.conf", system_id)
            os.remove(system_id_path)
            try:
                self.disable_yum_rhn_plugin()
            except Exception:
                pass
            print _("System successfully unregistered from legacy server.")
        else:
            # If the legacy server reports that deletion just failed, then quit.
            system_exit(1, _("Unable to unregister system from legacy server.  ") + SEE_LOG_FILE)

    def load_transition_data(self, rpc_session):
        # We need to send up the entire contents of the systemid file which is referred to in
        # Satellite 5 nomenclature as a "certificate" although it is not an X509 certificate.
        system_id = open(self.rhncfg["systemIdPath"], 'r').read()
        try:
            transition_data = rpc_session.system.transitionDataForSystem(system_id)
            self.consumer_id = transition_data['uuid']
        except Exception, e:
            log.exception(e)
            system_exit(1, _("Could not retrieve system migration data from legacy server.  ") + SEE_LOG_FILE)

    def consumer_exists(self, consumer_id):
        try:
            self.cp.getConsumer(consumer_id)
            return True
        except Exception, e:
            log.exception(e)
            print _("Consumer %s doesn't exist.  Creating new consumer.") % consumer_id
            return False

    def register(self, credentials, org, environment):
        # For registering the machine, use the CLI tool to reuse the username/password (because the GUI will prompt for them again)
        # Prepended a \n so translation can proceed without hitch
        print ("")
        print _("Attempting to register system to destination server...")
        cmd = ['subscription-manager', 'register', '--username=' + credentials.username, '--password=' + credentials.password]
        if self.options.destination_url:
            cmd.append('--serverurl=' + self.options.destination_url)

        if org:
            cmd.append('--org=' + org)
        if environment:
            cmd.append('--environment=' + environment)

        if self.options.five_to_six:
            if self.consumer_exists(self.consumer_id):
                cmd.append('--consumerid=' + self.consumer_id)

        if self.options.auto:
            cmd.append('--auto-attach')

        if self.options.service_level:
            servicelevel = self.select_service_level(org, self.options.service_level)
            cmd.append('--servicelevel=' + servicelevel)

        subprocess.call(cmd)

        identity = inj.require(inj.IDENTITY)
        identity.reload()

        if not identity.is_valid():
            system_exit(2, _("\nUnable to register.\nFor further assistance, please contact Red Hat Global Support Services."))

        print _("System '%s' successfully registered.\n") % identity.name
        return identity

    def select_service_level(self, org, servicelevel):
        not_supported = _("Error: The service-level command is not supported by the server.")
        try:
            levels = self.cp.getServiceLevelList(org)
        except RemoteServerException, e:
            system_exit(-1, not_supported)
        except RestlibException, e:
            if e.code == 404:
                # no need to die, just skip it
                print not_supported
                return None
            else:
                # server supports it but something went wrong, die.
                raise e

        # Create the sla tuple before appending the empty string to the list of
        # valid slas.
        slas = [(sla, sla) for sla in levels]
        # Display an actual message for the empty string level.
        slas.append((_("No service level preference"), ""))

        # The empty string is a valid level so append it to the list.
        levels.append("")
        if servicelevel is None or \
            servicelevel.upper() not in (level.upper() for level in levels):
            if servicelevel is not None:
                print _("\nService level \"%s\" is not available.") % servicelevel
            menu = Menu(slas, _("Please select a service level agreement for this system."))
            servicelevel = menu.choose()
        return servicelevel

    def enable_extra_channels(self, subscribed_channels):
        # Check if system was subscribed to extra channels like supplementary, optional, fastrack etc.
        # If so, enable them in the redhat.repo file
        extra_channels = {'supplementary': False, 'productivity': False, 'optional': False}
        for subscribedChannel in subscribed_channels:
            if 'supplementary' in subscribedChannel:
                extra_channels['supplementary'] = True
            elif 'optional' in subscribedChannel:
                extra_channels['optional'] = True
            elif 'productivity' in subscribedChannel:
                extra_channels['productivity'] = True

        if True not in extra_channels.values():
            return

        # create and populate the redhat.repo file
        # use the injection cp_providers consumer auth
        repolib.RepoActionInvoker().update()

        # read in the redhat.repo file
        repofile = repolib.RepoFile()
        repofile.read()

        # enable any extra channels we are using and write out redhat.repo
        try:
            for rhsmChannel in repofile.sections():
                if ((extra_channels['supplementary'] and re.search('supplementary$', rhsmChannel)) or
                (extra_channels['optional'] and re.search('optional-rpms$', rhsmChannel)) or
                (extra_channels['productivity'] and re.search('productivity-rpms$', rhsmChannel))):
                    log.info("Enabling extra channel '%s'" % rhsmChannel)
                    repofile.set(rhsmChannel, 'enabled', '1')
            repofile.write()
        except Exception:
            print _("\nCouldn't enable extra repositories.")
            command = "subscription-manager repos --help"
            print _("Please ensure system has subscriptions attached, and see '%s' to enable additional repositories") % command

    def main(self, args=None):
        self.get_auth()
        self.transfer_http_proxy_settings()
        self.cp = self.get_candlepin_basic_auth_connection(self.destination_creds.username, self.destination_creds.password)
        self.check_ok_to_proceed(self.destination_creds.username)

        (rpc_session, session_key) = self.connect_to_rhn(self.legacy_creds)
        if self.options.five_to_six:
            self.load_transition_data(rpc_session)
            org = None
            environment = None
        else:
            org = self.get_org(self.destination_creds.username)
            environment = self.get_environment(org)

        # TODO Not sure this is necessary.  See BZ 1086367
        self.check_is_org_admin(rpc_session, session_key, self.legacy_creds.username)

        print
        print _("Retrieving existing legacy subscription information...")
        subscribed_channels = self.get_subscribed_channels_list(rpc_session, session_key)
        self.print_banner(_("System is currently subscribed to these legacy channels:"))
        for channel in subscribed_channels:
            print channel

        self.check_for_conflicting_channels(subscribed_channels)
        self.deploy_prod_certificates(subscribed_channels)
        self.clean_up(subscribed_channels)

        self.write_migration_facts()

        if self.options.registration_state == "purge":
            print
            print _("Preparing to unregister system from legacy server...")
            self.legacy_purge(rpc_session, session_key)
        elif self.options.registration_state == "unentitle":
            self.legacy_unentitle(rpc_session)
        else:
            # For the "keep" case, we just leave everything alone.
            pass

        identity = self.register(self.destination_creds, org, environment)
        if identity:
            self.enable_extra_channels(subscribed_channels)


def add_parser_options(parser, five_to_six_script=False):
    # Careful, the option is --no-auto but we are storing the opposite of its value.
    parser.add_option("-n", "--no-auto", action="store_false", default=True, dest="auto",
        help=_("don't execute the auto-attach option while registering with subscription manager"))
    parser.add_option("-s", "--servicelevel", dest="service_level",
        help=_("service level to follow when attaching subscriptions, for no service "
            "level use --servicelevel=\"\""))
    # See BZ 915847 - some users want to connect to RHN with a proxy but to RHSM without a proxy
    parser.add_option("--no-proxy", action="store_true", dest='noproxy',
        help=_("don't use legacy proxy settings with destination server"))
    if five_to_six_script:
        valid_states = ["keep", "unentitle", "purge"]
        parser.add_option("--registration-state", type="choice",
            choices=valid_states, metavar=",".join(valid_states), default="unentitle",
            help=_("state to leave system in on legacy server (not available in hosted environments; default is 'unentitle')"))
    else:
        # The consumerid provides these
        parser.add_option("--org", dest='org',
            help=_("organization to register to"))
        parser.add_option("--environment", dest='environment',
            help=_("environment to register to"))
        parser.add_option("-f", "--force", action="store_true", default=False,
            help=_("ignore channels not available on destination server"))

    parser.add_option("--legacy-user",
        help=_("specify the user name on the legacy server"))
    parser.add_option("--legacy-password",
        help=_("specify the password on the legacy server"))
    parser.add_option("--destination-url",
        help=_("specify the subscription management server to migrate to"))
    parser.add_option("--destination-user",
        help=_("specify the user name on the destination server"))
    parser.add_option("--destination-password",
        help=_("specify the password on the destination server"))


def validate_options(options):
    if options.service_level and not options.auto:
        # TODO Need to explain why this restriction exists.
        system_exit(1, _("The --servicelevel and --no-auto options cannot be used together."))


def is_hosted():
    rhsmcfg = rhsm.config.initConfig()
    hostname = rhsmcfg.get('server', 'hostname')
    return bool(re.search('subscription\.rhn\.(.*\.)*redhat\.com', hostname))


def set_defaults(options, five_to_six_script):
    options.five_to_six = five_to_six_script
    if five_to_six_script:
        options.org = None
        options.environment = None
        options.force = True
    else:
        options.registration_state = "purge"


def main(args=None, five_to_six_script=False):
    parser = OptionParser(usage=USAGE, formatter=WrappedIndentedHelpFormatter())
    add_parser_options(parser, five_to_six_script)

    # In testing we sometimes specify args, otherwise use the default:
    if not args:
        args = sys.argv[1:]

    (options, args) = parser.parse_args(args)
    set_defaults(options, five_to_six_script)
    validate_options(options)
    MigrationEngine(options).main()


if __name__ == '__main__':
    main()
