# 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)