aboutsummaryrefslogtreecommitdiff
path: root/infra/build/functions/request_build.py
blob: 543bafb33ad7f89d820b8dfe331731a3d20e149f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# 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 google.auth
from google.cloud import ndb

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(
        f'Project {project_name} not available in cloud datastore')

  return project.project_yaml_contents, project.dockerfile_contents


def get_empty_config():
  """Returns an empty build config."""
  return build_project.Config(False, None, None, False)


def get_build_steps(project_name, image_project, base_images_project):
  """Retrieve build steps."""
  # TODO(metzman): Figure out if we need this.
  project_yaml_contents, dockerfile_lines = get_project_data(project_name)
  build_config = get_empty_config()
  return build_project.get_build_steps(project_name, project_yaml_contents,
                                       dockerfile_lines, image_project,
                                       base_images_project, build_config)


def run_build(oss_fuzz_project, build_steps, credentials, build_type,
              cloud_project):
  """Execute build on cloud build. Wrapper around build_project.py that also
  updates the db."""
  build_id = build_project.run_build(oss_fuzz_project, build_steps, credentials,
                                     build_type, cloud_project)
  update_build_history(oss_fuzz_project, build_id, build_type)


# 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, cloud_project = google.auth.default()
    build_steps = get_build_steps(project_name, cloud_project, BASE_PROJECT)
    if not build_steps:
      return
    run_build(
        project_name,
        build_steps,
        credentials,
        build_project.FUZZING_BUILD_TYPE,
        cloud_project=cloud_project,
    )