blob: 87507201f8c71f679392ef791c40d9ef1495e981 [file] [log] [blame]
# Copyright 2017 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Various URI related helpers."""
import os
import re
import urllib.parse
def _ExtractGobClAndSubpath(o):
"""Return a (CL, subpath) tuple if found in the URI parts, or None if not.
Many forms are supported below.
"""
path = o.path
fragment = o.fragment
# https://chromium-review.googlesource.com/#/662618
# https://chromium-review.googlesource.com/#/662618/
# https://chromium-review.googlesource.com/#/662618/////
if path == "/":
m = re.match(r"^/(?:c/)?([0-9]+)/*$", fragment)
if m:
return (m.group(1), "")
# Any valid /c/ URI can also be in the fragment.
if fragment.startswith("/c/"):
# https://chrome-internal-review.googlesource.com/#/c/280497
# https://chrome-internal-review.googlesource.com/#/c/280497/
path = fragment
fragment = ""
else:
return None
# https://chromium-review.googlesource.com/662618
# https://chromium-review.googlesource.com/662618/
m = re.match(r"^/([0-9]+)/?$", path)
if m:
return (m.group(1), fragment)
# https://chromium-review.googlesource.com/c/662618
# https://chromium-review.googlesource.com/c/662618/
# https://chromium-review.googlesource.com/c/662618/1/lib/gs.py
m = re.match(r"^/c/([0-9]+)(/.*)?$", path)
if m:
return (m.group(1) + (m.group(2) or "").rstrip("/"), fragment)
# https://chromium-review.googlesource.com/c/chromiumos/chromite/+/662618
# https://chromium-review.googlesource.com/c/chromiumos/chromite/+/662618/
# https://chromium-review.googlesource.com/c/chromiumos/chromite/+/662618/1/lib/gs.py
m = re.match(r"^/c/.*/\+/([0-9]+)(/.*)?$", path)
if m:
return (m.group(1) + (m.group(2) or "").rstrip("/"), fragment)
return None
def _ShortenGob(o):
"""Shorten a Gerrit-on-Borg URI.
Args:
o: The named tuple from a urllib.parse.urlsplit call.
Returns:
A new named tuple that can be passed to urllib.parse.urlunsplit.
"""
parts = _ExtractGobClAndSubpath(o)
if parts is None:
return o
# If we're using a Chromium GoB, use crrev.com. Otherwise, we'll just
# shorten the path but keep the original GoB host.
if o.netloc.split(".")[0] in ("chromium-review", "chrome-internal-review"):
netloc = "crrev.com"
if o.netloc.startswith("chrome-internal"):
sub = "i"
else:
sub = "c"
path = "/%s/%s" % (sub, parts[0])
else:
netloc = o.netloc
path = "/%s" % (parts[0],)
return o._replace(
scheme="https", netloc=netloc, path=path, fragment=parts[1]
)
def _ShortenCrosReview(o):
"""Shorten old review to new review hosts.
Args:
o: The named tuple from a urllib.parse.urlsplit call.
Returns:
A new named tuple that can be passed to urllib.parse.urlunsplit.
"""
m = re.match(r"^/(i/)?([0-9]+)", o.path)
if m:
subpart = "i" if m.group(1) else "c"
o = o._replace(
netloc="crrev.com", path="/%s/%s" % (subpart, m.group(2))
)
return o._replace(scheme="https")
def _ShortenRietveld(o):
"""Shorten a rietveld URI.
Args:
o: The named tuple from a urllib.parse.urlsplit call.
Returns:
A new named tuple that can be passed to urllib.parse.urlunsplit.
"""
m = re.match(r"^/([0-9]+)", o.path)
if m:
o = o._replace(netloc="crrev.com", path="/%s" % (m.group(1),))
return o._replace(scheme="https")
def _ShortenBuganizer(o):
"""Shorten a buganizer URI.
Args:
o: The named tuple from a urllib.parse.urlsplit call.
Returns:
A new named tuple that can be passed to urllib.parse.urlunsplit.
"""
if o.path == "/issue":
# http://b.corp.google.com/issue?id=123
qs = urllib.parse.parse_qs(o.query)
if "id" in qs:
o = o._replace(path="/%s" % (qs["id"][0],), query="")
elif o.path.startswith("/issues/"):
# http://b.corp.google.com/issues/123
o = o._replace(path=o.path[len("/issues") :])
return o._replace(scheme="http", netloc="b")
def _ShortenChromiumBug(o):
"""Shorten a Chromium bug URI.
Args:
o: The named tuple from a urllib.parse.urlsplit call.
Returns:
A new named tuple that can be passed to urllib.parse.urlunsplit.
"""
# https://bugs.chromium.org/p/chromium/issues/detail?id=123
# https://bugs.chromium.org/p/google-breakpad/issues/list
# These we don't actually shorten:
# https://bugs.chromium.org/p/chromium/people
m = re.match(r"/p/([^/]+)/issues(/detail)?", o.path)
if m is None:
return o
if m.group(1) == "chromium":
path = ""
else:
path = "/%s" % (m.group(1),)
if m.group(2):
qs = urllib.parse.parse_qs(o.query)
if "id" in qs:
path += "/%s" % (qs["id"][0],)
o = o._replace(query="")
return o._replace(scheme="https", netloc="crbug.com", path=path)
def _ShortenGutsTicket(o):
"""Shorten a Google GUTS ticket URI.
Args:
o: The named tuple from a urllib.parse.urlsplit call.
Returns:
A new named tuple that can be passed to urllib.parse.urlunsplit.
"""
# https://gutsv3.corp.google.com/#ticket/123
m = re.match(r"^ticket/([0-9]+)", o.fragment)
if m:
o = o._replace(path="/%s" % (m.group(1),), fragment="")
return o._replace(scheme="http", netloc="t")
# Map sites to the shortener. Use a tuple to keep rule ordering correct (even
# if we don't have rules atm relying on it).
_SHORTENERS = (
(re.compile(r"^[a-z0-9-]+-review\.googlesource\.com$"), _ShortenGob),
(re.compile(r"^crosreview\.com$"), _ShortenCrosReview),
(re.compile(r"^codereview\.chromium\.org$"), _ShortenRietveld),
(re.compile(r"^b\.corp\.google\.com$"), _ShortenBuganizer),
(re.compile(r"^bugs\.chromium\.org$"), _ShortenChromiumBug),
(re.compile(r"^gutsv\d\.corp\.google\.com$"), _ShortenGutsTicket),
)
def ShortenUri(uri, omit_scheme=False):
"""Attempt to shorten a URI for humans.
If the URI can't be shortened, then we just return the original value.
Thus this can be safely used as a pass-through for anything.
Args:
uri: Any valid URI.
omit_scheme: Whether to include the scheme (e.g. http:// or https://).
Returns:
A (hopefully shorter) URI pointing to the same resource as |uri|.
"""
o = urllib.parse.urlsplit(uri)
# If the scheme & host are empty, assume it's because the URI we were given
# lacked a http:// or https:// prefix, so blindly insert a http://.
if not o.scheme and not o.netloc:
o = urllib.parse.urlsplit("http://%s" % (uri,))
for matcher, shortener in _SHORTENERS:
if matcher.match(o.netloc):
o = shortener(o)
break
else:
return uri
if omit_scheme:
o = o._replace(scheme="")
# Strip off the leading // due to blank scheme.
return urllib.parse.urlunsplit(o)[2:]
else:
return urllib.parse.urlunsplit(o)
_LUCI_MILO_BUILDBOT_URL = "https://luci-milo.appspot.com/buildbot"
_LOGDOG_URL = (
"https://luci-logdog.appspot.com/v/"
"?s=chromeos/buildbucket/cr-buildbucket.appspot.com/"
"%s/%%2B/steps/%s/0/stdout"
)
# Will redirect:
# https://ci.chromium.org/b/8914470887449121184
# to:
# https://ci.chromium.org/p/chromeos/builds/b8914470887449121184
_MILO_BUILD_URL = "https://ci.chromium.org/b/%(buildbucket_id)s"
def ConstructMiloBuildUri(buildbucket_id):
"""Return a Milo build URL.
Args:
buildbucket_id: Buildbucket id of the build to link.
Returns:
The fully formed URI.
"""
# Only local tryjobs will not have a buildbucket_id, but they also do not
# have a web UI to point at. Generate a fake URL.
buildbucket_id = buildbucket_id or "fake_bb_id"
return _MILO_BUILD_URL % {"buildbucket_id": buildbucket_id}
def ConstructDashboardUri(buildbot_primary_name, builder_name, build_number):
"""Return the dashboard (luci-milo) URL for this run
Args:
buildbot_primary_name: Name of buildbot primary, e.g. chromeos
builder_name: Builder name on buildbot dashboard.
build_number: Build number for this validation attempt.
Returns:
The fully formed URI.
"""
url_suffix = "%s/%s" % (builder_name, str(build_number))
url_suffix = urllib.parse.quote(url_suffix)
return os.path.join(
_LUCI_MILO_BUILDBOT_URL, buildbot_primary_name, url_suffix
)
def ConstructLogDogUri(build_number, stage):
"""Return the logdog URL for the given build number and stage.
Args:
build_number: The build ID.
stage: The name of the stage to view logs for.
Returns:
The fully formed URI.
"""
return _LOGDOG_URL % (str(build_number), stage)
def ConstructViceroyBuildDetailsUri(build_id):
"""Return the dashboard (viceroy) URL for this run.
Args:
build_id: CIDB id for the primary build.
Returns:
The fully formed URI.
"""
_link = (
"https://viceroy.corp.google.com/"
"chromeos/build_details?build_id=%(build_id)s"
)
return _link % {"build_id": build_id}