| # Copyright 2020 Google Inc. |
| # |
| # 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. |
| # |
| ################################################################################ |
| """Cloud function to request builds.""" |
| import base64 |
| import logging |
| |
| import google.auth |
| from googleapiclient.discovery import build |
| from google.cloud import ndb |
| |
| import build_lib |
| import build_project |
| from datastore_entities import BuildsHistory |
| from datastore_entities import Project |
| |
| BASE_PROJECT = 'oss-fuzz-base' |
| MAX_BUILD_HISTORY_LENGTH = 64 |
| QUEUE_TTL_SECONDS = 60 * 60 * 24 # 24 hours. |
| |
| |
| def update_build_history(project_name, build_id, build_tag): |
| """Update build history of project.""" |
| project_key = ndb.Key(BuildsHistory, project_name + '-' + build_tag) |
| project = project_key.get() |
| |
| if not project: |
| project = BuildsHistory(id=project_name + '-' + build_tag, |
| build_tag=build_tag, |
| project=project_name, |
| build_ids=[]) |
| |
| if len(project.build_ids) >= MAX_BUILD_HISTORY_LENGTH: |
| project.build_ids.pop(0) |
| |
| project.build_ids.append(build_id) |
| project.put() |
| |
| |
| def get_project_data(project_name): |
| """Retrieve project metadata from datastore.""" |
| query = Project.query(Project.name == project_name) |
| project = query.get() |
| if not project: |
| raise RuntimeError( |
| 'Project {0} not available in cloud datastore'.format(project_name)) |
| project_yaml_contents = project.project_yaml_contents |
| dockerfile_lines = project.dockerfile_contents.split('\n') |
| |
| return (project_yaml_contents, dockerfile_lines) |
| |
| |
| def get_build_steps(project_name, image_project, base_images_project): |
| """Retrieve build steps.""" |
| project_yaml_contents, dockerfile_lines = get_project_data(project_name) |
| return build_project.get_build_steps(project_name, project_yaml_contents, |
| dockerfile_lines, image_project, |
| base_images_project) |
| |
| |
| # pylint: disable=no-member |
| def run_build(project_name, image_project, build_steps, credentials, tag): |
| """Execute build on cloud build.""" |
| build_body = { |
| 'steps': build_steps, |
| 'timeout': str(build_lib.BUILD_TIMEOUT) + 's', |
| 'options': { |
| 'machineType': 'N1_HIGHCPU_32' |
| }, |
| 'logsBucket': build_project.GCB_LOGS_BUCKET, |
| 'tags': [project_name + '-' + tag,], |
| 'queueTtl': str(QUEUE_TTL_SECONDS) + 's', |
| } |
| |
| cloudbuild = build('cloudbuild', |
| 'v1', |
| credentials=credentials, |
| cache_discovery=False) |
| build_info = cloudbuild.projects().builds().create(projectId=image_project, |
| body=build_body).execute() |
| build_id = build_info['metadata']['build']['id'] |
| |
| update_build_history(project_name, build_id, tag) |
| logging.info('Build ID: %s', build_id) |
| logging.info('Logs: %s', build_project.get_logs_url(build_id, image_project)) |
| |
| |
| # pylint: disable=no-member |
| def request_build(event, context): |
| """Entry point for cloud function to request builds.""" |
| del context #unused |
| if 'data' in event: |
| project_name = base64.b64decode(event['data']).decode('utf-8') |
| else: |
| raise RuntimeError('Project name missing from payload') |
| |
| with ndb.Client().context(): |
| credentials, image_project = google.auth.default() |
| build_steps = get_build_steps(project_name, image_project, BASE_PROJECT) |
| if not build_steps: |
| return |
| run_build(project_name, image_project, build_steps, credentials, |
| build_project.FUZZING_BUILD_TAG) |