• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021 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"""Module for getting the configuration CIFuzz needs to run."""
15
16import logging
17import enum
18import os
19import json
20
21import environment
22
23
24def _get_project_repo_name():
25  return os.path.basename(environment.get('GITHUB_REPOSITORY', ''))
26
27
28def _get_pr_ref(event):
29  if event == 'pull_request':
30    return environment.get('GITHUB_REF')
31  return None
32
33
34def _get_sanitizer():
35  return os.getenv('SANITIZER', 'address').lower()
36
37
38def _get_project_name():
39  # TODO(metzman): Remove OSS-Fuzz reference.
40  return os.getenv('OSS_FUZZ_PROJECT_NAME')
41
42
43def _is_dry_run():
44  """Returns True if configured to do a dry run."""
45  return environment.get_bool('DRY_RUN', 'false')
46
47
48def get_project_src_path(workspace):
49  """Returns the manually checked out path of the project's source if specified
50  or None."""
51  # TODO(metzman): Get rid of MANUAL_SRC_PATH when Skia switches to
52  # PROJECT_SRC_PATH.
53  path = os.getenv('PROJECT_SRC_PATH', os.getenv('MANUAL_SRC_PATH'))
54  if not path:
55    logging.debug('No PROJECT_SRC_PATH.')
56    return path
57
58  logging.debug('PROJECT_SRC_PATH set.')
59  if os.path.isabs(path):
60    return path
61
62  # If |src| is not absolute, assume we are running in GitHub actions.
63  # TODO(metzman): Don't make this assumption.
64  return os.path.join(workspace, path)
65
66
67DEFAULT_LANGUAGE = 'c++'
68
69
70def _get_language():
71  """Returns the project language."""
72  # Get language from environment. We took this approach because the convenience
73  # given to OSS-Fuzz users by not making them specify the language again (and
74  # getting it from the project.yaml) is outweighed by the complexity in
75  # implementing this. A lot of the complexity comes from our unittests not
76  # setting a proper projet at this point.
77  return os.getenv('LANGUAGE', DEFAULT_LANGUAGE)
78
79
80# pylint: disable=too-few-public-methods,too-many-instance-attributes
81
82
83class BaseConfig:
84  """Object containing constant configuration for CIFuzz."""
85
86  class Platform(enum.Enum):
87    """Enum representing the different platforms CIFuzz runs on."""
88    EXTERNAL_GITHUB = 0  # Non-OSS-Fuzz on GitHub actions.
89    INTERNAL_GITHUB = 1  # OSS-Fuzz on GitHub actions.
90    INTERNAL_GENERIC_CI = 2  # OSS-Fuzz on any CI.
91
92  def __init__(self):
93    self.workspace = os.getenv('GITHUB_WORKSPACE')
94    self.project_name = _get_project_name()
95    # Check if failures should not be reported.
96    self.dry_run = _is_dry_run()
97    self.sanitizer = _get_sanitizer()
98    self.build_integration_path = os.getenv('BUILD_INTEGRATION_PATH')
99    self.language = _get_language()
100    event_path = os.getenv('GITHUB_EVENT_PATH')
101    self.is_github = bool(event_path)
102    logging.debug('Is github: %s.', self.is_github)
103    # TODO(metzman): Parse env like we do in ClusterFuzz.
104    self.low_disk_space = environment.get('LOW_DISK_SPACE', False)
105
106  @property
107  def is_internal(self):
108    """Returns True if this is an OSS-Fuzz project."""
109    return not self.build_integration_path
110
111  @property
112  def platform(self):
113    """Returns the platform CIFuzz is runnning on."""
114    if not self.is_internal:
115      return self.Platform.EXTERNAL_GITHUB
116    if self.is_github:
117      return self.Platform.INTERNAL_GITHUB
118    return self.Platform.INTERNAL_GENERIC_CI
119
120
121class RunFuzzersConfig(BaseConfig):
122  """Class containing constant configuration for running fuzzers in CIFuzz."""
123
124  RUN_FUZZERS_MODES = {'batch', 'ci'}
125
126  def __init__(self):
127    super().__init__()
128    self.fuzz_seconds = int(os.environ.get('FUZZ_SECONDS', 600))
129    self.run_fuzzers_mode = os.environ.get('RUN_FUZZERS_MODE', 'ci').lower()
130    if self.run_fuzzers_mode not in self.RUN_FUZZERS_MODES:
131      raise Exception(
132          ('Invalid RUN_FUZZERS_MODE %s not one of allowed choices: %s.' %
133           self.run_fuzzers_mode, self.RUN_FUZZERS_MODES))
134
135
136class BuildFuzzersConfig(BaseConfig):
137  """Class containing constant configuration for building fuzzers in CIFuzz."""
138
139  def _get_config_from_event_path(self, event):
140    event_path = os.getenv('GITHUB_EVENT_PATH')
141    if not event_path:
142      return
143    with open(event_path, encoding='utf-8') as file_handle:
144      event_data = json.load(file_handle)
145    if event == 'push':
146      self.base_commit = event_data['before']
147      logging.debug('base_commit: %s', self.base_commit)
148    else:
149      self.pr_ref = 'refs/pull/{0}/merge'.format(
150          event_data['pull_request']['number'])
151      logging.debug('pr_ref: %s', self.pr_ref)
152
153    self.git_url = event_data['repository']['html_url']
154
155  def __init__(self):
156    """Get the configuration from CIFuzz from the environment. These variables
157    are set by GitHub or the user."""
158    # TODO(metzman): Some of this config is very CI-specific. Move it into the
159    # CI class.
160    super().__init__()
161    self.project_repo_name = _get_project_repo_name()
162    self.commit_sha = os.getenv('GITHUB_SHA')
163    event = os.getenv('GITHUB_EVENT_NAME')
164
165    self.pr_ref = None
166    self.git_url = None
167    self.base_commit = None
168    self._get_config_from_event_path(event)
169
170    self.base_ref = os.getenv('GITHUB_BASE_REF')
171    self.project_src_path = get_project_src_path(self.workspace)
172
173    self.allowed_broken_targets_percentage = os.getenv(
174        'ALLOWED_BROKEN_TARGETS_PERCENTAGE')
175    self.bad_build_check = environment.get_bool('BAD_BUILD_CHECK', 'true')
176
177    # TODO(metzman): Use better system for interpreting env vars. What if env
178    # var is set to '0'?
179    self.keep_unaffected_fuzz_targets = bool(
180        os.getenv('KEEP_UNAFFECTED_FUZZERS'))
181