blob: ad2cd36c614192772b318fd0c87816794664a4fb [file] [log] [blame]
# Copyright 2021 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.
"""Module for getting the configuration CIFuzz needs to run."""
import logging
import enum
import os
import json
import environment
def _get_project_repo_name():
return os.path.basename(environment.get('GITHUB_REPOSITORY', ''))
def _get_pr_ref(event):
if event == 'pull_request':
return environment.get('GITHUB_REF')
return None
def _get_sanitizer():
return os.getenv('SANITIZER', 'address').lower()
def _get_project_name():
# TODO(metzman): Remove OSS-Fuzz reference.
return os.getenv('OSS_FUZZ_PROJECT_NAME')
def _is_dry_run():
"""Returns True if configured to do a dry run."""
return environment.get_bool('DRY_RUN', 'false')
def get_project_src_path(workspace):
"""Returns the manually checked out path of the project's source if specified
or None."""
# TODO(metzman): Get rid of MANUAL_SRC_PATH when Skia switches to
# PROJECT_SRC_PATH.
path = os.getenv('PROJECT_SRC_PATH', os.getenv('MANUAL_SRC_PATH'))
if not path:
logging.debug('No PROJECT_SRC_PATH.')
return path
logging.debug('PROJECT_SRC_PATH set.')
if os.path.isabs(path):
return path
# If |src| is not absolute, assume we are running in GitHub actions.
# TODO(metzman): Don't make this assumption.
return os.path.join(workspace, path)
DEFAULT_LANGUAGE = 'c++'
def _get_language():
"""Returns the project language."""
# Get language from environment. We took this approach because the convenience
# given to OSS-Fuzz users by not making them specify the language again (and
# getting it from the project.yaml) is outweighed by the complexity in
# implementing this. A lot of the complexity comes from our unittests not
# setting a proper projet at this point.
return os.getenv('LANGUAGE', DEFAULT_LANGUAGE)
# pylint: disable=too-few-public-methods,too-many-instance-attributes
class BaseConfig:
"""Object containing constant configuration for CIFuzz."""
class Platform(enum.Enum):
"""Enum representing the different platforms CIFuzz runs on."""
EXTERNAL_GITHUB = 0 # Non-OSS-Fuzz on GitHub actions.
INTERNAL_GITHUB = 1 # OSS-Fuzz on GitHub actions.
INTERNAL_GENERIC_CI = 2 # OSS-Fuzz on any CI.
def __init__(self):
self.workspace = os.getenv('GITHUB_WORKSPACE')
self.project_name = _get_project_name()
# Check if failures should not be reported.
self.dry_run = _is_dry_run()
self.sanitizer = _get_sanitizer()
self.build_integration_path = os.getenv('BUILD_INTEGRATION_PATH')
self.language = _get_language()
event_path = os.getenv('GITHUB_EVENT_PATH')
self.is_github = bool(event_path)
logging.debug('Is github: %s.', self.is_github)
# TODO(metzman): Parse env like we do in ClusterFuzz.
self.low_disk_space = environment.get('LOW_DISK_SPACE', False)
@property
def is_internal(self):
"""Returns True if this is an OSS-Fuzz project."""
return not self.build_integration_path
@property
def platform(self):
"""Returns the platform CIFuzz is runnning on."""
if not self.is_internal:
return self.Platform.EXTERNAL_GITHUB
if self.is_github:
return self.Platform.INTERNAL_GITHUB
return self.Platform.INTERNAL_GENERIC_CI
class RunFuzzersConfig(BaseConfig):
"""Class containing constant configuration for running fuzzers in CIFuzz."""
RUN_FUZZERS_MODES = {'batch', 'ci'}
def __init__(self):
super().__init__()
self.fuzz_seconds = int(os.environ.get('FUZZ_SECONDS', 600))
self.run_fuzzers_mode = os.environ.get('RUN_FUZZERS_MODE', 'ci').lower()
if self.run_fuzzers_mode not in self.RUN_FUZZERS_MODES:
raise Exception(
('Invalid RUN_FUZZERS_MODE %s not one of allowed choices: %s.' %
self.run_fuzzers_mode, self.RUN_FUZZERS_MODES))
class BuildFuzzersConfig(BaseConfig):
"""Class containing constant configuration for building fuzzers in CIFuzz."""
def _get_config_from_event_path(self, event):
event_path = os.getenv('GITHUB_EVENT_PATH')
if not event_path:
return
with open(event_path, encoding='utf-8') as file_handle:
event_data = json.load(file_handle)
if event == 'push':
self.base_commit = event_data['before']
logging.debug('base_commit: %s', self.base_commit)
else:
self.pr_ref = 'refs/pull/{0}/merge'.format(
event_data['pull_request']['number'])
logging.debug('pr_ref: %s', self.pr_ref)
self.git_url = event_data['repository']['html_url']
def __init__(self):
"""Get the configuration from CIFuzz from the environment. These variables
are set by GitHub or the user."""
# TODO(metzman): Some of this config is very CI-specific. Move it into the
# CI class.
super().__init__()
self.project_repo_name = _get_project_repo_name()
self.commit_sha = os.getenv('GITHUB_SHA')
event = os.getenv('GITHUB_EVENT_NAME')
self.pr_ref = None
self.git_url = None
self.base_commit = None
self._get_config_from_event_path(event)
self.base_ref = os.getenv('GITHUB_BASE_REF')
self.project_src_path = get_project_src_path(self.workspace)
self.allowed_broken_targets_percentage = os.getenv(
'ALLOWED_BROKEN_TARGETS_PERCENTAGE')
self.bad_build_check = environment.get_bool('BAD_BUILD_CHECK', 'true')
# TODO(metzman): Use better system for interpreting env vars. What if env
# var is set to '0'?
self.keep_unaffected_fuzz_targets = bool(
os.getenv('KEEP_UNAFFECTED_FUZZERS'))