#!/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.
#
# Migration Script (for bz 726407) which will :
# 1] install subscription-manger and depdendent packages
# 2] determine the subscribed channels on RHN Classic
# 3] copy the corresponding RHSM product certificates to /etc/pki/product
# 4] unregister the system from RHN Classic
# 5] transfer any http proxy settings from the up2date to rhsm.conf files
# 6] run the subscription-manager tool to register the system and consume
#   a subscription
#
# Authors: Paresh Mutha <pmutha@redhat.com>
#          Mark Huth <mhuth@redhat.com>
#################################################

import sys
import os
import xmlrpclib
import getpass
import libxml2
import subprocess
import re
import simplejson as json
import shutil
import logging
import traceback
from rhsm.connection import UEPConnection, RestlibException

_LIBPATH = "/usr/share/rhsm"
# add to the path if need be
if _LIBPATH not in sys.path:
    sys.path.append(_LIBPATH)

from subscription_manager.i18n import configure_i18n
configure_i18n()

import gettext
_ = gettext.gettext

# quick check to see if you are a super-user.
if os.getuid() != 0:
    print _("Must be root user to execute\n")
    sys.exit(8)

# access the rhn/up2date python libaries and read the up2date config file
_RHNLIBPATH = "/usr/share/rhn"
if _RHNLIBPATH not in sys.path:
    sys.path.append(_RHNLIBPATH)

from up2date_client import up2dateErrors
from up2date_client.rhnChannel import  getChannels
import up2date_client.config

rhncfg = up2date_client.config.initUp2dateConfig()

from optparse import Option, OptionParser
options_table = [
    Option("-f", "--force", action="store_true", default=False,
           help=_("Ignore Channels not available on RHSM")),
    Option("-c", "--cli-only", action="store_true", default=False, dest='clionly',
           help=_("Don't launch the GUI tool to subscribe the system, just use the CLI tool which will do it automatically")),
    Option("-n", "--no-auto", action="store_true", default=False, dest='noauto',
           help=_("Don't execute the autosubscribe option while registering with subscription manager.")),
]

parser = OptionParser(option_list=options_table, usage="%s [--force|--cli-only|--help|--no-auto]" % sys.argv[0])
(options, args) = parser.parse_args()

# access the rhsm python libraries, read rhsm config file and setup logging
_RHSMLIBPATH = "/usr/share/rhsm"
if _RHSMLIBPATH not in sys.path:
    sys.path.append(_RHSMLIBPATH)

try:
    # try importing from RHEL 5 location
    from subscription_manager.certlib import ConsumerIdentity, ProductDirectory
    from subscription_manager import repolib, logutil
except ImportError:
    # new RHEL 6 location
    from certlib import ConsumerIdentity, ProductDirectory
    import repolib
    import logutil

import rhsm.config

logutil.init_logger()
log = logging.getLogger('rhsm-app.' + __name__)
rhsmcfg = rhsm.config.initConfig()


def systemExit(code, msgs=None):
    "Exit with a code and optional message(s). Saved a few lines of code."
    if msgs:
        if type(msgs) not in [type([]), type(())]:
            msgs = (msgs, )
        for msg in msgs:
            sys.stderr.write(str(msg) + '\n')
    sys.exit(code)


def checkOkToProceed(username, password):
    # check if this machine is already registered to Certicate-based RHN
    if ConsumerIdentity.existsAndValid():
        print _("\nThis machine appears to be already registered to Certificate-based RHN.  Exiting.")
        consumer = ConsumerIdentity.read()
        systemExit(1, _("\nPlease visit https://access.redhat.com/management/consumers/%s to view the profile details.") % consumer.getConsumerId())

    # Check to make sure we're hooked up to Hosted.
    serverURL = rhncfg['serverURL']
    if not re.search('xmlrpc\.rhn\..*redhat\.com', serverURL):
        log.info("serverURL = %s" % serverURL)
        systemExit(1, _("This migration script requires the system to be registered to RHN Hosted Classic.\nHowever this system appears to be registered to '%s'.\nExiting.") % serverURL.split('/')[2])

    # Check to make sure we can connect to the certificate server.
    cp = UEPConnection(username=username, password=password)

    try:
        cp.getOwnerList(username)
    except RestlibException:
        systemExit(1, _("Unable to connect to certificate server.  See /var/log/rhsm/rhsm.log for more details."))


def getSubscribedChannelsList():
    try:
        subscribedChannels = map(lambda x: x['label'], getChannels().channels())
    except up2dateErrors.NoChannelsError:
        systemExit(1, _("This system is not associated with any channel."))
    except up2dateErrors.NoSystemIdError:
        systemExit(1, _("Unable to locate SystemId file. Is this system registered?"))
    except:
        log.error(traceback.format_exc())
        systemExit(1, _("Problem encountered getting the list of subscribed channels.  Exiting."))
    return subscribedChannels


def getSystemId():
    systemIdPath = rhncfg["systemIdPath"]
    p = libxml2.parseDoc(file(systemIdPath).read())
    systemId = int(p.xpathEval('string(//member[* = "system_id"]/value/string)').split('-')[1])
    return systemId


def unRegisterSystemFromRhnClassic(username, password):
    #getSystemIdPath
    systemIdPath = rhncfg["systemIdPath"]
    systemId = getSystemId()

    log.info("Deleting system %s from RHN Classic...", systemId)
    hostname = rhncfg['serverURL'].split('/')[2]
    server_url = 'https://%s/rpc/api' % (hostname)
    try:
        sc = xmlrpclib.Server(server_url)
        sk = sc.auth.login(username, password)
        result = sc.system.deleteSystems(sk, systemId)
        if result:
            log.info("System %s deleted.  Removing systemid file and disabling rhnplugin.conf", systemId)
            os.remove(systemIdPath)
            disableYumRhnPlugin()
            print _("System successfully unregistered from RHN Classic.")
        else:
            systemExit(1, _("Unable to unregister system from RHN Classic.  Exiting."))
    except:
        log.error(traceback.format_exc())
        systemExit(1, _("Problem encountered unregistering system from RHN Classic.  Exiting."))


def disableYumRhnPlugin():
    # '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")
    YUM_PLUGIN_CONF = '/etc/yum/pluginconf.d/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 readChannelCertMapping():
    release = getRelease()
    mappingfile = "/usr/share/rhsm/product/" + release + "/channel-cert-mapping.txt"
    log.info("Using mapping file %s", mappingfile)
    f = open(mappingfile)
    lines = f.readlines()
    dic_data = {}
    for line in lines:
        if re.match("^[a-zA-Z]", line):
            line = line.replace("\n", "")
            key, val = line.split(": ")
            dic_data[key] = val
    return dic_data


def transferHttpProxySettings():
    # transfer http proxy information from up2date to rhsm.conf
    if rhncfg['enableProxy'] and not rhsmcfg.get('server', 'proxy_hostname'):
        proxy_hostname, proxy_port = rhncfg['httpProxy'].split(':')
        log.info("Using proxy %s:%s - transferring settings to rhsm.conf" % (proxy_hostname, proxy_port))
        rhsmcfg.set('server', 'proxy_hostname', proxy_hostname)
        rhsmcfg.set('server', 'proxy_port', proxy_port)
        if rhncfg['enableProxyAuth']:
            rhsmcfg.set('server', 'proxy_user', rhncfg['proxyUser'])
            rhsmcfg.set('server', 'proxy_password', rhncfg['proxyPassword'])
        rhsmcfg.save()


def runSubscriptionManager(username, password):
    # For registering the machine, use the CLI tool to reuse the username/password (because the GUI will prompt for them again)
    print _("\nAttempting to register system to Certificate-based RHN ...")
    result = subprocess.call(['subscription-manager', 'register', '--username=' + username, '--password=' + password])
    if result != 0:
        systemExit(2, _("\nUnable to register.\nFor further assistance, please contact Red Hat Global Support Services."))
    else:
        consumer = ConsumerIdentity.read()
        print _("System '%s' successfully registered to Certificate-based RHN.\n") % consumer.getConsumerName()

    # For subscribing, use the GUI tool if the DISPLAY environment variable is set and the gui tool exists
    if os.getenv('DISPLAY') and os.path.exists('/usr/bin/subscription-manager-gui') and not options.clionly:
        print _("Launching the GUI tool to manually subscribe the system ...")
        result = subprocess.call(['subscription-manager-gui'], stderr=open(os.devnull, 'w'))
    else:
        print _("Attempting to auto-subscribe to appropriate subscriptions ...")
        result = subprocess.call(['subscription-manager', 'subscribe', '--auto'])
        if result != 0:
            print _("\nUnable to auto-subscribe.  Do your existing subscriptions match the products installed on this system?")
    print _("\nPlease visit https://access.redhat.com/management/consumers/%s to view the details, and to make changes if necessary.") % consumer.getConsumerId()


def deployProdCertificates(subscribedChannels):
    dic_data = readChannelCertMapping()

    applicableCerts = []
    validRhsmChannels = []
    invalidRhsmChannels = []
    unrecognizedChannels = []

    for channel in subscribedChannels:
        try:
            if dic_data[channel] != 'none':
                validRhsmChannels.append(channel)
                log.info("mapping found for : %s = %s", channel, dic_data[channel])
                if dic_data[channel] not in applicableCerts:
                    applicableCerts.append(dic_data[channel])
            else:
                invalidRhsmChannels.append(channel)
                log.info("%s None", channel)
        except:
            unrecognizedChannels.append(channel)

    if invalidRhsmChannels:
        print "\n+--------------------------------------------------+"
        print _("Below mentioned channels are NOT available on RHSM")
        print "+--------------------------------------------------+"
        for i in invalidRhsmChannels:
            print i

    if unrecognizedChannels:
        print   "\n+---------------------------------------------------------------------------------------+",
        print _("\nUnrecognized channels. Channel to Product Certificate mapping missing for these channels.")
        print   "+---------------------------------------------------------------------------------------+"
        for i in unrecognizedChannels:
            print i

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

    log.info("certs to be copied: %s", applicableCerts)

    print _("\nList of channels for which certs are being copied")
    for i in validRhsmChannels:
        print i


    release = getRelease()

    # creates the product directory if it doesn't already exist
    productDir = ProductDirectory()
    for cert in applicableCerts:
        sourcepath = "/usr/share/rhsm/product/" + release + "/" + cert
        truncated_cert_name = cert.split('-')[-1]
        destinationpath = str(productDir) + "/" + truncated_cert_name
        log.info("cp %s %s ", sourcepath, destinationpath)
        shutil.copy2(sourcepath, destinationpath)
    print _("\nProduct Certificates copied successfully to %s !!") % str(productDir)


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


def enableExtraChannels(subscribedChannels):
    # Check if system was subscribed to extra channels like supplementary, optional, fastrack etc.
    # If so, enable them in the redhat.repo file

    extraChannels = {'supplementary':False, 'productivity':False, 'optional':False}
    for subscribedChannel in subscribedChannels:
        if 'supplementary' in subscribedChannel:
            extraChannels['supplementary'] = True
        elif 'optional' in subscribedChannel:
            extraChannels['optional'] = True
        elif 'productivity' in subscribedChannel:
            extraChannels['productivity'] = True

    if True not in extraChannels.values():
        return

    # create and populate the redhat.repo file
    repolib.RepoLib().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 ((extraChannels['supplementary'] and re.search('supplementary$', rhsmChannel)) or
            (extraChannels['optional']  and re.search('optional-rpms$', rhsmChannel)) or
            (extraChannels['productivity']  and re.search('productivity-rpms$', rhsmChannel))):
                log.info("Enabling extra channel '%s'" % rhsmChannel)
                repofile.set(rhsmChannel, 'enabled', '1')
                repofile.write()
    except:
        print _("\nUnable to enable extra repositories.")
        print _("Please ensure system is subscribed to entitlements, and alter /etc/yum.repos.d/redhat.repo to enable extra repositories.")


def writeMigrationFacts():
    FACT_FILE = "/etc/rhsm/facts/migration.facts"
    if not os.path.exists(FACT_FILE):
        f = open(FACT_FILE, 'w')
        json.dump({"migration.classic_system_id": getSystemId(),
                   "migration.migrated_from": "rhn_hosted_classic"}, f)
        f.close()


def main():
    username = raw_input(_("RHN Username: ")).strip()
    password = getpass.getpass()
    checkOkToProceed(username, password)

    # get a list of RHN classic channels this machine is subscribed to
    print _("\nRetrieving existing RHN classic subscription information ...")
    subscribedChannels = getSubscribedChannelsList()
    print "+----------------------------------+"
    print _("System is currently subscribed to:")
    print "+----------------------------------+"
    for channel in subscribedChannels:
        print channel

    deployProdCertificates(subscribedChannels)

    writeMigrationFacts()
    print _("\nPreparing to unregister system from RHN classic ...")
    unRegisterSystemFromRhnClassic(username, password)

    # register the system to Certificate-based RHN and consume a subscription
    if not options.noauto:
        transferHttpProxySettings()
        runSubscriptionManager(username, password)

    # check if we need to enable to supplementary/optional channels
    enableExtraChannels(subscribedChannels)

if __name__ == '__main__':
    main()
