1# Copyright 2020 Google Inc. 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################################################################################ 16"""Cloud function to request builds.""" 17import base64 18 19import google.auth 20from google.cloud import ndb 21 22import build_project 23from datastore_entities import BuildsHistory 24from datastore_entities import Project 25 26BASE_PROJECT = 'oss-fuzz-base' 27MAX_BUILD_HISTORY_LENGTH = 64 28QUEUE_TTL_SECONDS = 60 * 60 * 24 # 24 hours. 29 30 31def update_build_history(project_name, build_id, build_tag): 32 """Update build history of project.""" 33 project_key = ndb.Key(BuildsHistory, project_name + '-' + build_tag) 34 project = project_key.get() 35 36 if not project: 37 project = BuildsHistory(id=project_name + '-' + build_tag, 38 build_tag=build_tag, 39 project=project_name, 40 build_ids=[]) 41 42 if len(project.build_ids) >= MAX_BUILD_HISTORY_LENGTH: 43 project.build_ids.pop(0) 44 45 project.build_ids.append(build_id) 46 project.put() 47 48 49def get_project_data(project_name): 50 """Retrieve project metadata from datastore.""" 51 query = Project.query(Project.name == project_name) 52 project = query.get() 53 if not project: 54 raise RuntimeError( 55 f'Project {project_name} not available in cloud datastore') 56 57 return project.project_yaml_contents, project.dockerfile_contents 58 59 60def get_empty_config(): 61 """Returns an empty build config.""" 62 return build_project.Config(False, None, None, False) 63 64 65def get_build_steps(project_name, image_project, base_images_project): 66 """Retrieve build steps.""" 67 # TODO(metzman): Figure out if we need this. 68 project_yaml_contents, dockerfile_lines = get_project_data(project_name) 69 build_config = get_empty_config() 70 return build_project.get_build_steps(project_name, project_yaml_contents, 71 dockerfile_lines, image_project, 72 base_images_project, build_config) 73 74 75def run_build(oss_fuzz_project, build_steps, credentials, build_type, 76 cloud_project): 77 """Execute build on cloud build. Wrapper around build_project.py that also 78 updates the db.""" 79 build_id = build_project.run_build(oss_fuzz_project, build_steps, credentials, 80 build_type, cloud_project) 81 update_build_history(oss_fuzz_project, build_id, build_type) 82 83 84# pylint: disable=no-member 85def request_build(event, context): 86 """Entry point for cloud function to request builds.""" 87 del context #unused 88 if 'data' in event: 89 project_name = base64.b64decode(event['data']).decode('utf-8') 90 else: 91 raise RuntimeError('Project name missing from payload') 92 93 with ndb.Client().context(): 94 credentials, cloud_project = google.auth.default() 95 build_steps = get_build_steps(project_name, cloud_project, BASE_PROJECT) 96 if not build_steps: 97 return 98 run_build( 99 project_name, 100 build_steps, 101 credentials, 102 build_project.FUZZING_BUILD_TYPE, 103 cloud_project=cloud_project, 104 ) 105