#!/usr/bin/python3 # -*- coding: utf-8 -*- # # (C) 2007 Canonical Ltd., Steve Kowalik # Authors: # Martin Pitt # Steve Kowalik # Michael Bienia # Daniel Hahler # Iain Lane # Jonathan Davies # Markus Korn (python-launchpadlib support) # # ################################################################## # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; version 2. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # See file /usr/share/common-licenses/GPL-2 for more details. # # ################################################################## import optparse import os import sys from distro_info import UbuntuDistroInfo from ubuntutools import getLogger from ubuntutools.config import UDTConfig, ubu_email from ubuntutools.lp import udtexceptions from ubuntutools.misc import require_utf8 from ubuntutools.question import EditBugReport, confirmation_prompt from ubuntutools.version import Version Logger = getLogger() # # entry point # def main(): # Our usage options. usage = "Usage: %prog [options] [ [base version]]" parser = optparse.OptionParser(usage) parser.add_option( "-d", type="string", dest="dist", default="unstable", help="Debian distribution to sync from.", ) parser.add_option( "-k", type="string", dest="keyid", default=None, help="GnuPG key ID to use for signing report " "(only used when emailing the sync request).", ) parser.add_option( "-n", action="store_true", dest="newpkg", default=False, help="Whether package to sync is a new package in Ubuntu.", ) parser.add_option( "--email", action="store_true", default=False, help="Use a PGP-signed email for filing the sync request, rather than the LP API.", ) parser.add_option( "--lp", dest="deprecated_lp_flag", action="store_true", default=False, help=optparse.SUPPRESS_HELP, ) parser.add_option( "-l", "--lpinstance", metavar="INSTANCE", dest="lpinstance", default=None, help="Launchpad instance to connect to (default: production).", ) parser.add_option( "-s", action="store_true", dest="sponsorship", default=False, help="Force sponsorship" ) parser.add_option( "-C", action="store_true", dest="missing_changelog_ok", default=False, help="Allow changelog to be manually filled in when missing", ) parser.add_option( "-e", action="store_true", dest="ffe", default=False, help="Use this after FeatureFreeze for non-bug fix " "syncs, changes default subscription to the " "appropriate release team.", ) parser.add_option( "--no-conf", action="store_true", dest="no_conf", default=False, help="Don't read config files or environment variables", ) (options, args) = parser.parse_args() if not len(args): parser.print_help() sys.exit(1) require_utf8() config = UDTConfig(options.no_conf) if options.deprecated_lp_flag: Logger.info("The --lp flag is now default, ignored.") if options.email: options.lpapi = False else: options.lpapi = config.get_value("USE_LPAPI", default=True, boolean=True) if options.lpinstance is None: options.lpinstance = config.get_value("LPINSTANCE") if options.keyid is None: options.keyid = config.get_value("KEYID") if not options.lpapi: if options.lpinstance == "production": bug_mail_domain = "bugs.launchpad.net" elif options.lpinstance == "staging": bug_mail_domain = "bugs.staging.launchpad.net" else: Logger.error("Error: Unknown launchpad instance: %s" % options.lpinstance) sys.exit(1) mailserver_host = config.get_value( "SMTP_SERVER", default=None, compat_keys=["UBUSMTP", "DEBSMTP"] ) if not options.lpapi and not mailserver_host: try: import DNS DNS.DiscoverNameServers() mxlist = DNS.mxlookup(bug_mail_domain) firstmx = mxlist[0] mailserver_host = firstmx[1] except ImportError: Logger.error("Please install python-dns to support Launchpad mail server lookup.") sys.exit(1) mailserver_port = config.get_value( "SMTP_PORT", default=25, compat_keys=["UBUSMTP_PORT", "DEBSMTP_PORT"] ) mailserver_user = config.get_value("SMTP_USER", compat_keys=["UBUSMTP_USER", "DEBSMTP_USER"]) mailserver_pass = config.get_value("SMTP_PASS", compat_keys=["UBUSMTP_PASS", "DEBSMTP_PASS"]) # import the needed requestsync module if options.lpapi: from ubuntutools.lp.lpapicache import Distribution, Launchpad from ubuntutools.requestsync.lp import ( check_existing_reports, get_debian_srcpkg, get_ubuntu_delta_changelog, get_ubuntu_srcpkg, need_sponsorship, post_bug, ) # See if we have LP credentials and exit if we don't - # cannot continue in this case try: # devel for changelogUrl() Launchpad.login(service=options.lpinstance, api_version="devel") except IOError: sys.exit(1) else: from ubuntutools.requestsync.mail import ( check_existing_reports, get_debian_srcpkg, get_ubuntu_delta_changelog, get_ubuntu_srcpkg, mail_bug, need_sponsorship, ) if not any(x in os.environ for x in ("UBUMAIL", "DEBEMAIL", "EMAIL")): Logger.error( "The environment variable UBUMAIL, DEBEMAIL or EMAIL needs " "to be set to let this script mail the sync request." ) sys.exit(1) newsource = options.newpkg sponsorship = options.sponsorship distro = options.dist ffe = options.ffe lpapi = options.lpapi need_interaction = False force_base_version = None srcpkg = args[0] if len(args) == 1: if lpapi: release = Distribution("ubuntu").getDevelopmentSeries().name else: ubu_info = UbuntuDistroInfo() release = ubu_info.devel() Logger.warning("Target release missing - assuming %s" % release) elif len(args) == 2: release = args[1] elif len(args) == 3: release = args[1] force_base_version = Version(args[2]) else: Logger.error("Too many arguments.") parser.print_help() sys.exit(1) # Get the current Ubuntu source package try: ubuntu_srcpkg = get_ubuntu_srcpkg(srcpkg, release, "Proposed") ubuntu_version = Version(ubuntu_srcpkg.getVersion()) ubuntu_component = ubuntu_srcpkg.getComponent() newsource = False # override the -n flag except udtexceptions.PackageNotFoundException: ubuntu_srcpkg = None ubuntu_version = Version("~") ubuntu_component = None # Set after getting the Debian info if not newsource: Logger.info("'%s' doesn't exist in 'Ubuntu %s'." % (srcpkg, release)) Logger.info("Do you want to sync a new package?") confirmation_prompt() newsource = True except udtexceptions.SeriesNotFoundException as error: Logger.error(error) sys.exit(1) # Get the requested Debian source package try: debian_srcpkg = get_debian_srcpkg(srcpkg, distro) debian_version = Version(debian_srcpkg.getVersion()) debian_component = debian_srcpkg.getComponent() except udtexceptions.PackageNotFoundException as error: Logger.error(error) sys.exit(1) except udtexceptions.SeriesNotFoundException as error: Logger.error(error) sys.exit(1) if ubuntu_component is None: if debian_component == "main": ubuntu_component = "universe" else: ubuntu_component = "multiverse" # Stop if Ubuntu has already the version from Debian or a newer version if (ubuntu_version >= debian_version) and options.lpapi: # try rmadison import ubuntutools.requestsync.mail try: debian_srcpkg = ubuntutools.requestsync.mail.get_debian_srcpkg(srcpkg, distro) debian_version = Version(debian_srcpkg.getVersion()) debian_component = debian_srcpkg.getComponent() except udtexceptions.PackageNotFoundException as error: Logger.error(error) sys.exit(1) if ubuntu_version == debian_version: Logger.error( "The versions in Debian and Ubuntu are the " "same already (%s). Aborting." % ubuntu_version ) sys.exit(1) if ubuntu_version > debian_version: Logger.error( "The version in Ubuntu (%s) is newer than " "the version in Debian (%s). Aborting." % (ubuntu_version, debian_version) ) sys.exit(1) # -s flag not specified - check if we do need sponsorship if not sponsorship: sponsorship = need_sponsorship(srcpkg, ubuntu_component, release) if not sponsorship and not ffe: Logger.error( "Consider using syncpackage(1) for syncs that " "do not require feature freeze exceptions." ) # Check for existing package reports if not newsource: check_existing_reports(srcpkg) # Generate bug report pkg_to_sync = "%s %s (%s) from Debian %s (%s)" % ( srcpkg, debian_version, ubuntu_component, distro, debian_component, ) title = "Sync %s" % pkg_to_sync if ffe: title = "FFe: " + title report = "Please sync %s\n\n" % pkg_to_sync if "ubuntu" in str(ubuntu_version): need_interaction = True Logger.info("Changes have been made to the package in Ubuntu.") Logger.info("Please edit the report and give an explanation.") Logger.info("Not saving the report file will abort the request.") report += ( "Explanation of the Ubuntu delta and why it can be " "dropped:\n%s\n>>> ENTER_EXPLANATION_HERE <<<\n\n" % get_ubuntu_delta_changelog(ubuntu_srcpkg) ) if ffe: need_interaction = True Logger.info("To approve FeatureFreeze exception, you need to state") Logger.info("the reason why you feel it is necessary.") Logger.info("Not saving the report file will abort the request.") report += "Explanation of FeatureFreeze exception:\n>>> ENTER_EXPLANATION_HERE <<<\n\n" if need_interaction: confirmation_prompt() base_version = force_base_version or ubuntu_version if newsource: report += "All changelog entries:\n\n" else: report += "Changelog entries since current %s version %s:\n\n" % (release, ubuntu_version) changelog = debian_srcpkg.getChangelog(since_version=base_version) if not changelog: if not options.missing_changelog_ok: Logger.error( "Did not retrieve any changelog entries. " "Do you need to specify '-C'? " "Was the package recently uploaded? (check " "http://packages.debian.org/changelogs/)" ) sys.exit(1) else: need_interaction = True changelog = "XXX FIXME: add changelog here XXX" report += changelog editor = EditBugReport(title, report) editor.edit(optional=not need_interaction) title, report = editor.get_report() if "XXX FIXME" in report: Logger.error( "changelog boilerplate found in report, " "please manually add changelog when using '-C'" ) sys.exit(1) # bug status and bug subscriber status = "confirmed" subscribe = "ubuntu-archive" if sponsorship: status = "new" subscribe = "ubuntu-sponsors" if ffe: status = "new" subscribe = "ubuntu-release" srcpkg = not newsource and srcpkg or None if lpapi: # Map status to the values expected by LP API mapping = {"new": "New", "confirmed": "Confirmed"} # Post sync request using LP API post_bug(srcpkg, subscribe, mapping[status], title, report) else: email_from = ubu_email(export=False)[1] # Mail sync request mail_bug( srcpkg, subscribe, status, title, report, bug_mail_domain, options.keyid, email_from, mailserver_host, mailserver_port, mailserver_user, mailserver_pass, ) if __name__ == "__main__": try: main() except KeyboardInterrupt: Logger.error("User abort.") sys.exit(2)