1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
|
# software-properties PPA support, using launchpadlib
#
# Copyright (c) 2019 Canonical Ltd.
#
# Original Author: Michael Vogt <mvo@debian.org>
# Rewrite: Dan Streetman <ddstreet@canonical.com>
#
# 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; either version 2 of the
# License, or (at your option) any later version.
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
from gettext import gettext as _
from lazr.restfulclient.errors import (NotFound, BadRequest, Unauthorized)
from softwareproperties.shortcuthandler import (ShortcutHandler, ShortcutException,
InvalidShortcutException)
from softwareproperties.sourceslist import SourcesListShortcutHandler
from softwareproperties.uri import URIShortcutHandler
from aptsources.sourceslist import Deb822SourceEntry
from urllib.parse import urlparse
try:
from launchpadlib.launchpad import Launchpad
except ImportError as e:
Launchpad = None
PPA_URI_FORMAT = 'https://ppa.launchpadcontent.net/{team}/{ppa}/ubuntu/'
PRIVATE_PPA_URI_FORMAT = 'https://private-ppa.launchpadcontent.net/{team}/{ppa}/ubuntu/'
PPA_VALID_HOSTNAMES = [
urlparse(PPA_URI_FORMAT).hostname,
urlparse(PRIVATE_PPA_URI_FORMAT).hostname,
# Old hostnames.
'ppa.launchpad.net',
'private-ppa.launchpad.net',
]
PPA_VALID_COMPS = ['main', 'main/debug']
class PPAShortcutHandler(ShortcutHandler):
def __init__(self, shortcut, login=False, lp=None, **kwargs):
super(PPAShortcutHandler, self).__init__(shortcut, deb822=True, **kwargs)
self._lp_anon = not login
self._signing_key_data = None
self._lp = lp # LP object
self._lpteam = None # Person/Team LP object
self._lpppa = None # PPA Archive LP object
self._is_sourceslist = False
# one of these will set teamname and ppaname, and maybe _source_entry
if not self._match_ppa(shortcut):
# The input is either a sources.list line or a URI. Both cases lead
# to the SourcesListShortcutHandler being used, so unset
# self.deb822 (LP: #2037210).
self.deb822 = False
if not any((self._match_uri(shortcut),
self._match_sourceslist(shortcut))):
msg = (_("ERROR: '%s' is not a valid ppa format") % shortcut)
raise InvalidShortcutException(msg)
self._filebase = "%s-ubuntu-%s" % (self.teamname, self.ppaname)
self._set_auth()
# Make sure we can find/access the PPA, lp:#1965180
if self._is_sourceslist:
try:
self.lpppa
except ShortcutException:
raise InvalidShortcutException(_("ERROR: Can't find ppa"))
if not self._source_entry:
comps = self.components
if not comps:
comps = ['main']
if self.lpppa.publish_debug_symbols:
print("PPA publishes dbgsym, you may need to include 'main/debug' component")
# comps += ['main/debug']
uri_format = PRIVATE_PPA_URI_FORMAT if self.lpppa.private else PPA_URI_FORMAT
uri = uri_format.format(team=self.teamname, ppa=self.ppaname)
entry = Deb822SourceEntry(None, '')
entry.types = [self.binary_type]
entry.uris = [uri]
entry.suites = [self.dist]
entry.comps = comps
self._set_source_entry(str(entry))
@property
def lp(self):
if not Launchpad:
return None
if not self._lp:
if self._lp_anon:
login_func = Launchpad.login_anonymously
else:
login_func = Launchpad.login_with
self._lp = login_func("%s.%s" % (self.__module__, self.__class__.__name__),
service_root='production',
version='devel')
return self._lp
@property
def lpteam(self):
if not self._lpteam:
try:
self._lpteam = self.lp.people(self.teamname)
except NotFound:
msg = (_("ERROR: user/team '%s' not found (use --login if private)") % self.teamname)
raise ShortcutException(msg)
except Unauthorized:
msg = (_("ERROR: invalid user/team name '%s'") % self.teamname)
raise ShortcutException(msg)
return self._lpteam
@property
def lpppa(self):
if not self._lpppa:
try:
self._lpppa = self.lpteam.getPPAByName(name=self.ppaname)
except NotFound:
msg = (_("ERROR: ppa '%s/%s' not found (use --login if private)") %
(self.teamname, self.ppaname))
raise ShortcutException(msg)
except BadRequest:
msg = (_("ERROR: invalid ppa name '%s'") % self.ppaname)
raise ShortcutException(msg)
return self._lpppa
@property
def description(self):
return self.lpppa.description
@property
def web_link(self):
return self.lpppa.web_link
@property
def trustedparts_content(self):
if not self._signing_key_data:
key = self.lpppa.getSigningKeyData()
fingerprint = self.lpppa.signing_key_fingerprint
if not fingerprint:
print(_("Warning: could not get PPA signing_key_fingerprint from LP, using anyway"))
elif 'redacted' in fingerprint:
print(_("Private PPA fingerprint redacted, using key anyway (LP: #1879781)"))
elif not fingerprint in self.fingerprints(key):
msg = (_("Fingerprints do not match, not importing: '%s' != '%s'") %
(fingerprint, ','.join(self.fingerprints(key))))
raise ShortcutException(msg)
self._signing_key_data = key
return self._signing_key_data
def SourceEntry(self, pkgtype=None):
entry = super(PPAShortcutHandler, self).SourceEntry(pkgtype=pkgtype)
if pkgtype != self.source_type or self.components:
return entry
# 'main/debug' is needed to get dbgsyms from ppas,
# but it's not a valid component for ppa deb-src lines.
# Sigh.
entry.comps = list(set(entry.comps) - set(['main/debug']))
return entry
def _set_source_entry(self, line):
super(PPAShortcutHandler, self)._set_source_entry(line)
invalid_comps = set(self.SourceEntry().comps) - set(PPA_VALID_COMPS)
if invalid_comps:
print(_("Warning: components '%s' not valid for PPA") % ' '.join(invalid_comps))
def _match_ppa(self, shortcut):
(prefix, _, ppa) = shortcut.rpartition(':')
if not prefix.lower() == 'ppa':
return False
(teamname, _, ppaname) = ppa.partition('/')
teamname = teamname.lstrip('~')
if '/' in ppaname:
(ubuntu, _, ppaname) = ppaname.partition('/')
if ubuntu.lower() != 'ubuntu':
# PPAs only support ubuntu
return False
if '/' in ppaname:
# Path is too long for valid ppa
return False
self.teamname = teamname
self.ppaname = ppaname or 'ppa'
return True
def _match_uri(self, shortcut):
try:
return self._match_handler(URIShortcutHandler(shortcut))
except InvalidShortcutException:
return False
def _match_sourceslist(self, shortcut):
try:
handler = self._match_handler(SourcesListShortcutHandler(shortcut))
except InvalidShortcutException:
return False
self._is_sourceslist = True
return handler
def _match_handler(self, handler):
parsed = urlparse(handler.SourceEntry().uri)
if not parsed.hostname in PPA_VALID_HOSTNAMES:
return False
path = parsed.path.strip().strip('/').split('/')
if len(path) < 2:
return False
self.teamname = path[0]
self.ppaname = path[1]
self._username = handler.username
self._password = handler.password
self._set_source_entry(handler.SourceEntry().line)
return True
def _set_auth(self):
if self._lp_anon or not self.lpppa.private:
return
if self._username and self._password:
return
try:
# Named ops work against actual person, not the me alias object
me = self.lp.people(self.lp.me.name)
url = me.getArchiveSubscriptionURL(archive=self.lpppa)
except Unauthorized:
msg = (_("Could not find PPA subscription for ppa:%s/%s, you may need to request access") %
(self.teamname, self.ppaname))
raise ShortcutException(msg)
else:
parsed = urlparse(url)
self._username = parsed.username
self._password = parsed.password
|