Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add standalone script for managing OSS-Fuzz -> OSV. #39

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions infra/.pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
[MESSAGES CONTROL]

# List of checkers and warnings to enable.
enable=
indexing-exception,
old-raise-syntax,

# List of checkers and warnings to disable.
# TODO: Shrink this list to as small as possible.
disable=
attribute-defined-outside-init,
bad-option-value,
bare-except,
broad-except,
c-extension-no-member,
design,
file-ignored,
fixme,
global-statement,
import-error,
import-outside-toplevel,
locally-disabled,
misplaced-comparison-constant,
multiple-imports,
no-member,
no-name-in-module,
no-self-use,
relative-import,
similarities,
suppressed-message,
ungrouped-imports,
unsubscriptable-object,
useless-object-inheritance, # Remove once all bots are on Python 3.
useless-suppression,
wrong-import-order,
wrong-import-position,
unspecified-encoding,
consider-using-with,
consider-using-f-string # TODO(ochang): remove this.


[BASIC]

# Regular expression which should only match the name
# of functions or classes which do not require a docstring.
no-docstring-rgx=(__.*__|main)

# Min length in lines of a function that requires a docstring.
docstring-min-length=10

# Regular expression which should only match correct module names. The
# leading underscore is sanctioned for private modules by Google's style
# guide.
#
# There are exceptions to the basic rule (_?[a-z][a-z0-9_]*) to cover
# requirements of Python's module system and of the presubmit framework.
module-rgx=^(_?[a-z][a-z0-9_]*)|__init__|__main__|PRESUBMIT|PRESUBMIT_unittest$

# Regular expression which should only match correct module level names.
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$

# Regular expression which should only match correct class attribute.
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$

# Regular expression which should only match correct class names.
class-rgx=^_?[A-Z][a-zA-Z0-9]*$

# Regular expression which should only match correct function names.
# 'camel_case' and 'snake_case' group names are used for consistency of naming
# styles across functions and methods.
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$

# Regular expression which should only match correct method names.
# 'camel_case' and 'snake_case' group names are used for consistency of naming
# styles across functions and methods. 'exempt' indicates a name which is
# consistent with all naming styles.
method-rgx=(?x)
^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase
|tearDownTestCase|setupSelf|tearDownClass|setUpClass
|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)
|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)
|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$

# Regular expression which should only match correct instance attribute names.
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$

# Regular expression which should only match correct argument names.
argument-rgx=^[a-z][a-z0-9_]*$

# Regular expression which should only match correct variable names.
variable-rgx=^[a-z][a-z0-9_]*$

# Regular expression which should only match correct list comprehension /
# generator expression variable names.
inlinevar-rgx=^[a-z][a-z0-9_]*$

# Good variable names which should always be accepted, separated by a comma.
good-names=main,_

# Bad variable names which should always be refused, separated by a comma.
bad-names=


[FORMAT]

# Maximum number of characters on a single line.
max-line-length=80

# Maximum number of lines in a module
max-module-lines=99999

# String used as indentation unit. We differ from PEP8's normal 4 spaces.
indent-string=' '


[TYPECHECK]

5 changes: 5 additions & 0 deletions infra/.style.yapf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[style]
based_on_style = yapf
column_limit = 80
indent_width = 2
split_before_named_assigns = true
Empty file added infra/README.md
Empty file.
1,071 changes: 1,071 additions & 0 deletions infra/poetry.lock

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions infra/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[tool.poetry]
name = "syncer"
version = "0.1.0"
description = ""
authors = ["Oliver Chang <[email protected]>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"
google-auth = "^2.36.0"
google-auth-httplib2 = "^0.2.0"
google-api-python-client = "^2.154.0"
google-cloud-datastore = "^2.20.1"
google-cloud-pubsub = "^2.27.1"
pyyaml = "^6.0.2"
google-cloud-storage = "^2.18.2"


[tool.poetry.group.dev.dependencies]
pylint = "^3.3.1"
yapf = "^0.43.0"
pyright = "^1.1.389"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
14 changes: 14 additions & 0 deletions infra/syncer/google_issue_tracker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Google issue tracker."""
64 changes: 64 additions & 0 deletions infra/syncer/google_issue_tracker/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Gets a Google Issue Tracker HTTP client."""

import google.auth
from google.auth import impersonated_credentials
import google_auth_httplib2
from googleapiclient import discovery
from googleapiclient import errors
import httplib2

_DISCOVERY_URL = ('https://issuetracker.googleapis.com/$discovery/rest?'
'version=v1&labels=GOOGLE_PUBLIC')
_SCOPE = 'https://www.googleapis.com/auth/buganizer'
# OSS-Fuzz service account.
_IMPERSONATED_SERVICE_ACCOUNT = (
'[email protected]')
_REQUEST_TIMEOUT = 60

HttpError = errors.HttpError
UnknownApiNameOrVersion = errors.UnknownApiNameOrVersion


def build_http():
"""Builds a httplib2.Http."""
source_credentials, _ = google.auth.default()
credentials = impersonated_credentials.Credentials(
source_credentials=source_credentials,
target_principal=_IMPERSONATED_SERVICE_ACCOUNT,
target_scopes=[_SCOPE])

return google_auth_httplib2.AuthorizedHttp(
credentials, http=httplib2.Http(timeout=_REQUEST_TIMEOUT))


def _call_discovery(api, http):
"""Calls the discovery service.

Retries upto twice if there are any UnknownApiNameOrVersion errors.
"""
return discovery.build(
api,
'v1',
discoveryServiceUrl=_DISCOVERY_URL,
http=http,
static_discovery=False)


def build(api='issuetracker', http=None):
"""Builds a google api client for buganizer."""
if not http:
http = build_http()
return _call_discovery(api, http)
92 changes: 92 additions & 0 deletions infra/syncer/google_issue_tracker/issue_tracker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=protected-access
"""Google issue tracker implementation."""

import enum

from google.auth import exceptions

from . import client

_NUM_RETRIES = 3


class IssueAccessLevel(str, enum.Enum):
LIMIT_NONE = 'LIMIT_NONE'
LIMIT_VIEW = 'LIMIT_VIEW'
LIMIT_APPEND = 'LIMIT_APPEND'
LIMIT_VIEW_TRUSTED = 'LIMIT_VIEW_TRUSTED'


class IssueTrackerError(Exception):
"""Base issue tracker error."""


class IssueTrackerNotFoundError(IssueTrackerError):
"""Not found error."""


class IssueTrackerPermissionError(IssueTrackerError):
"""Permission error."""


class IssueTracker:
"""Google issue tracker implementation."""

def __init__(self, http_client):
self._client = http_client

@property
def client(self):
"""HTTP Client."""
if self._client is None:
self._client = client.build()
return self._client

def _execute(self, request):
"""Executes a request."""
http = None
for _ in range(2):
try:
return request.execute(num_retries=_NUM_RETRIES, http=http)
except exceptions.RefreshError:
# Rebuild client and retry request.
http = client.build_http()
self._client = client.build('issuetracker', http=http)
return request.execute(num_retries=_NUM_RETRIES, http=http)
except client.HttpError as e:
if e.resp.status == 404:
raise IssueTrackerNotFoundError(str(e)) from e
if e.resp.status == 403:
raise IssueTrackerPermissionError(str(e)) from e
raise IssueTrackerError(str(e)) from e

def get_issue(self, issue_id):
"""Gets the issue with the given ID."""
return self._execute(self.client.issues().get(issueId=str(issue_id)))

def find_issues(self, query):
"""Finds issues."""
page_token = None
while True:
issues = self._execute(self.client.issues().list(
query=query, pageToken=page_token))
if 'issues' not in issues:
return
yield from issues['issues']
page_token = issues.get('nextPageToken')
if not page_token:
break
Loading