Skip to content

Commit f839558

Browse files
authored
chore: Migrate make_bugs_public to use Google issue tracker. (#2676)
OSS-Fuzz migrated to Google Issue Tracker from Monorail as part of Monorail being turned down. Client based on a very stripped down implementation of https://github.com/google/clusterfuzz/tree/e2c3d679fa6e4eac32718afa362c83cfb14fe06c/src/clusterfuzz/_internal/issue_management/google_issue_tracker, with some changes to make auth work (via impersonation).
1 parent 514150e commit f839558

File tree

7 files changed

+227
-78
lines changed

7 files changed

+227
-78
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright 2024 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Google issue tracker."""
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2023 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Gets a Google Issue Tracker HTTP client."""
15+
16+
import google.auth
17+
from google.auth import impersonated_credentials
18+
import google_auth_httplib2
19+
from googleapiclient import discovery
20+
from googleapiclient import errors
21+
import httplib2
22+
23+
_DISCOVERY_URL = ('https://issuetracker.googleapis.com/$discovery/rest?'
24+
'version=v1&labels=GOOGLE_PUBLIC')
25+
_SCOPE = 'https://www.googleapis.com/auth/buganizer'
26+
# OSS-Fuzz service account.
27+
_IMPERSONATED_SERVICE_ACCOUNT = (
28+
29+
_REQUEST_TIMEOUT = 60
30+
31+
HttpError = errors.HttpError
32+
UnknownApiNameOrVersion = errors.UnknownApiNameOrVersion
33+
34+
35+
def build_http():
36+
"""Builds a httplib2.Http."""
37+
source_credentials, _ = google.auth.default()
38+
credentials = impersonated_credentials.Credentials(
39+
source_credentials=source_credentials,
40+
target_principal=_IMPERSONATED_SERVICE_ACCOUNT,
41+
target_scopes=[_SCOPE])
42+
43+
return google_auth_httplib2.AuthorizedHttp(
44+
credentials, http=httplib2.Http(timeout=_REQUEST_TIMEOUT))
45+
46+
47+
def _call_discovery(api, http):
48+
"""Calls the discovery service.
49+
50+
Retries upto twice if there are any UnknownApiNameOrVersion errors.
51+
"""
52+
return discovery.build(
53+
api,
54+
'v1',
55+
discoveryServiceUrl=_DISCOVERY_URL,
56+
http=http,
57+
static_discovery=False)
58+
59+
60+
def build(api='issuetracker', http=None):
61+
"""Builds a google api client for buganizer."""
62+
if not http:
63+
http = build_http()
64+
return _call_discovery(api, http)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2023 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# pylint: disable=protected-access
16+
"""Google issue tracker implementation."""
17+
18+
import enum
19+
20+
from google.auth import exceptions
21+
22+
from . import client
23+
24+
_NUM_RETRIES = 3
25+
26+
27+
class IssueAccessLevel(str, enum.Enum):
28+
LIMIT_NONE = 'LIMIT_NONE'
29+
LIMIT_VIEW = 'LIMIT_VIEW'
30+
LIMIT_APPEND = 'LIMIT_APPEND'
31+
LIMIT_VIEW_TRUSTED = 'LIMIT_VIEW_TRUSTED'
32+
33+
34+
class IssueTrackerError(Exception):
35+
"""Base issue tracker error."""
36+
37+
38+
class IssueTrackerNotFoundError(IssueTrackerError):
39+
"""Not found error."""
40+
41+
42+
class IssueTrackerPermissionError(IssueTrackerError):
43+
"""Permission error."""
44+
45+
46+
class IssueTracker:
47+
"""Google issue tracker implementation."""
48+
49+
def __init__(self, http_client):
50+
self._client = http_client
51+
52+
@property
53+
def client(self):
54+
"""HTTP Client."""
55+
if self._client is None:
56+
self._client = client.build()
57+
return self._client
58+
59+
def _execute(self, request):
60+
"""Executes a request."""
61+
http = None
62+
for _ in range(2):
63+
try:
64+
return request.execute(num_retries=_NUM_RETRIES, http=http)
65+
except exceptions.RefreshError:
66+
# Rebuild client and retry request.
67+
http = client.build_http()
68+
self._client = client.build('issuetracker', http=http)
69+
return request.execute(num_retries=_NUM_RETRIES, http=http)
70+
except client.HttpError as e:
71+
if e.resp.status == 404:
72+
raise IssueTrackerNotFoundError(str(e)) from e
73+
if e.resp.status == 403:
74+
raise IssueTrackerPermissionError(str(e)) from e
75+
raise IssueTrackerError(str(e)) from e
76+
77+
def get_issue(self, issue_id):
78+
"""Gets the issue with the given ID."""
79+
return self._execute(self.client.issues().get(issueId=str(issue_id)))

docker/cron/make_bugs_public/make_bugs_public.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@
1717
import logging
1818
import sys
1919
from google.cloud import ndb
20-
import requests
2120

22-
import monorail
21+
from google_issue_tracker import client
22+
from google_issue_tracker import issue_tracker
2323
import osv
2424
import osv.logs
2525

26-
_MONORAIL_ACCOUNT = '[email protected]'
27-
2826

2927
def make_affected_commits_public(bug):
3028
"""Make related AffectedCommits entities public."""
@@ -37,7 +35,7 @@ def make_affected_commits_public(bug):
3735

3836
def main():
3937
"""Mark bugs public."""
40-
monorail_client = monorail.Client('oss-fuzz', _MONORAIL_ACCOUNT)
38+
tracker = issue_tracker.IssueTracker(client.build())
4139
query = osv.Bug.query(osv.Bug.public == False) # pylint: disable=singleton-comparison
4240

4341
to_mark_public = []
@@ -48,13 +46,14 @@ def main():
4846
continue
4947

5048
try:
51-
issue = monorail_client.get_issue(issue_id)
52-
except requests.exceptions.HTTPError:
49+
issue = tracker.get_issue(issue_id)
50+
except issue_tracker.IssueTrackerError:
5351
logging.error('Failed to get issue %s.', issue_id)
5452
continue
5553

56-
labels = [label['label'].lower() for label in issue['labels']]
57-
if 'restrict-view-commit' not in labels:
54+
if (issue['issueState'].get(
55+
'accessLimit',
56+
{}).get('accessLevel') == issue_tracker.IssueAccessLevel.LIMIT_NONE):
5857
bug.public = True
5958
logging.info('Marking %s as public.', bug.key.id())
6059
to_mark_public.append(bug)

docker/cron/make_bugs_public/monorail.py

Lines changed: 0 additions & 67 deletions
This file was deleted.

docker/worker/poetry.lock

Lines changed: 60 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker/worker/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package-mode = false
55
[tool.poetry.dependencies]
66
python = "^3.11"
77

8+
google-auth-httplib2 = "^0.2.0"
9+
google-api-python-client = "^2.147"
810
google-cloud-pubsub = "==2.23.1"
911
google-cloud-ndb = "==2.3.2"
1012
google-cloud-storage = "==2.18.2"

0 commit comments

Comments
 (0)