#!/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 argparse 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 = "%(prog)s [options] [ [base version]]" parser = argparse.ArgumentParser(usage=usage) parser.add_argument( "-d", dest="dist", default="unstable", help="Debian distribution to sync from." ) parser.add_argument( "-k", dest="keyid", default=None, help="GnuPG key ID to use for signing report " "(only used when emailing the sync request).", ) parser.add_argument( "-n", action="store_true", dest="newpkg", default=False, help="Whether package to sync is a new package in Ubuntu.", ) parser.add_argument( "--email", action="store_true", default=False, help="Use a PGP-signed email for filing the sync request, rather than the LP API.", ) parser.add_argument( "--lp", dest="deprecated_lp_flag", action="store_true", default=False, help=argparse.SUPPRESS, ) parser.add_argument( "-l", "--lpinstance", metavar="INSTANCE", dest="lpinstance", default=None, help="Launchpad instance to connect to (default: production).", ) parser.add_argument( "-s", action="store_true", dest="sponsorship", default=False, help="Force sponsorship" ) parser.add_argument( "-C", action="store_true", dest="missing_changelog_ok", default=False, help="Allow changelog to be manually filled in when missing", ) parser.add_argument( "-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_argument( "--no-conf", action="store_true", dest="no_conf", default=False, help="Don't read config files or environment variables", ) parser.add_argument("source_package", help=argparse.SUPPRESS) parser.add_argument("release", nargs="?", help=argparse.SUPPRESS) parser.add_argument("base_version", nargs="?", type=Version, help=argparse.SUPPRESS) args = parser.parse_args() require_utf8() config = UDTConfig(args.no_conf) if args.deprecated_lp_flag: Logger.info("The --lp flag is now default, ignored.") if args.email: args.lpapi = False else: args.lpapi = config.get_value("USE_LPAPI", default=True, boolean=True) if args.lpinstance is None: args.lpinstance = config.get_value("LPINSTANCE") if args.keyid is None: args.keyid = config.get_value("KEYID") if not args.lpapi: if args.lpinstance == "production": bug_mail_domain = "bugs.launchpad.net" elif args.lpinstance == "staging": bug_mail_domain = "bugs.staging.launchpad.net" else: Logger.error("Error: Unknown launchpad instance: %s", args.lpinstance) sys.exit(1) mailserver_host = config.get_value( "SMTP_SERVER", default=None, compat_keys=["UBUSMTP", "DEBSMTP"] ) if not args.lpapi and not mailserver_host: try: import DNS # pylint: disable=import-outside-toplevel 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 # pylint: disable=import-outside-toplevel if args.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=args.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 = args.newpkg sponsorship = args.sponsorship distro = args.dist ffe = args.ffe lpapi = args.lpapi need_interaction = False srcpkg = args.source_package if not args.release: if lpapi: args.release = Distribution("ubuntu").getDevelopmentSeries().name else: ubu_info = UbuntuDistroInfo() args.release = ubu_info.devel() Logger.warning("Target release missing - assuming %s", args.release) # Get the current Ubuntu source package try: ubuntu_srcpkg = get_ubuntu_srcpkg(srcpkg, args.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, args.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 args.lpapi: # try rmadison import ubuntutools.requestsync.mail # pylint: disable=import-outside-toplevel 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, args.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 = ( f"{srcpkg} {debian_version} ({ubuntu_component})" f" from Debian {distro} ({debian_component})" ) title = f"Sync {pkg_to_sync}" if ffe: title = "FFe: " + title report = f"Please sync {pkg_to_sync}\n\n" 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 += ( f"Explanation of the Ubuntu delta and why it can be dropped:\n" f"{get_ubuntu_delta_changelog(ubuntu_srcpkg)}\n>>> ENTER_EXPLANATION_HERE <<<\n\n" ) 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 = args.base_version or ubuntu_version if newsource: report += "All changelog entries:\n\n" else: report += f"Changelog entries since current {args.release} version {ubuntu_version}:\n\n" changelog = debian_srcpkg.getChangelog(since_version=base_version) if not changelog: if not args.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 = None if newsource else srcpkg 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, args.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)