Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d40c92f
feat(ci): Add public log URLs to build failure reports
hunsche Oct 19, 2025
83de133
fix(ci): Explicitly set logsBucket for dynamic builds
hunsche Oct 19, 2025
2449f3c
fix(ci): Add log URL to initial Phase 1 report
hunsche Oct 19, 2025
04b5b46
fix(ci): Add log URL to initial Phase 2 report
hunsche Oct 19, 2025
4e97b32
Merge branch 'master' into feat/add-public-log-urls
hunsche Oct 20, 2025
0809195
feat: Handle trial builds with no reports
hunsche Oct 20, 2025
3d9eabb
refactor: Centralize GCB build info logging
hunsche Oct 20, 2025
09bd2c2
refactor: Make GCB backoff less noisy
hunsche Oct 20, 2025
82fdb6d
fix: Improve error handling for trial build startup failures
hunsche Oct 20, 2025
42c456f
style: Apply yapf formatting
hunsche Oct 20, 2025
a1af255
Merge branch 'master' into feat/add-public-log-urls
hunsche Oct 20, 2025
04dad19
refactor: Simplify build result handling in _do_build_type_builds fun…
hunsche Oct 20, 2025
8bd3e3d
style: Adjust indentation for better readability in _do_build_type_bu…
hunsche Oct 20, 2025
614f62a
Merge branch 'master' into feat/add-public-log-urls
hunsche Oct 20, 2025
3ba8379
Fix(test): Update golden file for trial_build_test
hunsche Oct 20, 2025
21152aa
feat(build): Skip trial_build unless explicitly invoked
hunsche Oct 20, 2025
eb6d199
feat(build): Skip trial_build runs and create empty results
hunsche Oct 20, 2025
7696bc8
feat(build): Skip report_generator when not invoked explicitly
hunsche Oct 20, 2025
02d49bd
Style: Remove comments and apply formatting
hunsche Oct 20, 2025
605c7e3
Fix: Add missing logging import in report_generator
hunsche Oct 20, 2025
26a982e
Fix: Flush output buffer to ensure skip message is logged
hunsche Oct 20, 2025
36f2912
Fix: Pass project_name to run_build in trial_build
hunsche Oct 20, 2025
2cea774
Refactor: Remove logging and unused code in trial_build_main function
hunsche Oct 20, 2025
aba1f11
Fix: Simplify print statement in main function for clarity
hunsche Oct 20, 2025
54f117f
feat: Skip report generation for untriggered trial builds
hunsche Oct 20, 2025
0953c7d
fix: Correct flag file path and name for CI tests
hunsche Oct 21, 2025
712cfcc
fix: Correct GCS path parsing in get_signed_url
hunsche Oct 21, 2025
0fe1384
fix: Correct GCS path parsing in get_signed_url
hunsche Oct 21, 2025
582bef4
Merge branch 'master' into feat/add-public-log-urls
hunsche Oct 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions infra/build/functions/build_and_push_test_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,13 @@ def wait_for_build_and_report_summary(build_id, cloud_project='oss-fuzz-base'):
client_options=build_lib.REGIONAL_CLIENT_OPTIONS)
cloudbuild_api = cloudbuild.projects().builds()

logs_url = build_lib.get_gcb_url(build_id, cloud_project)
logging.info(
'================================================================')
logging.info(' PHASE 1: STARTED BASE IMAGE BUILD')
logging.info(
'----------------------------------------------------------------')
logging.info('GCB Build ID: %s', build_id)
logging.info('GCB Build URL: %s', logs_url)
for line in build_lib.get_build_info_lines(build_id, cloud_project):
logging.info(line)
logging.info(
'================================================================')

Expand All @@ -128,14 +127,13 @@ def wait_for_build_and_report_summary(build_id, cloud_project='oss-fuzz-base'):
logging.error('Error checking build status: %s', e)
time.sleep(15)

logs_url = build_lib.get_gcb_url(build_id, cloud_project)
logging.info(
'================================================================')
logging.info(' PHASE 1: BASE IMAGE BUILD REPORT')
logging.info(
'----------------------------------------------------------------')
logging.info('GCB Build ID: %s', build_id)
logging.info('GCB Build URL: %s', logs_url)
for line in build_lib.get_build_info_lines(build_id, cloud_project):
logging.info(line)
logging.info(
'================================================================')

Expand Down
37 changes: 22 additions & 15 deletions infra/build/functions/build_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,24 +282,19 @@ def get_signed_policy_document_upload_prefix(bucket, path_prefix):
)


# pylint: disable=no-member
def get_signed_url(path, method='PUT', content_type=''):
"""Returns a signed URL for |path|."""
timestamp = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
timestamp = timestamp.strftime('%Y-%m-%dT%H:%M:%SZ')

path = urlparse.urlparse(path)
blob_path = path.path.lstrip('/')
blob = f"""{method}
{content_type}
{timestamp}
/{path.netloc}/{blob_path}"""
"""Returns signed url."""
timestamp = int(time.time() + BUILD_TIMEOUT)
blob = f'{method}\n\n{content_type}\n{timestamp}\n{path}'

client_id, signature = _sign_blob(blob)
return (f'https://storage.googleapis.com/{path.netloc}/{blob_path}'
f'?GoogleAccessId={client_id}&Expires={int(time.time() + 3600)}'
f'&Signature={urlparse.quote_plus(signature)}')
values = {
'GoogleAccessId': client_id,
'Expires': timestamp,
'Signature': signature,
}
return f'https://storage.googleapis.com{path}?{urlparse.urlencode(values)}'


def _normalized_name(name):
Expand Down Expand Up @@ -685,6 +680,17 @@ def get_gcb_url(build_id, cloud_project='oss-fuzz'):
f'{build_id}?project={cloud_project}')


def get_build_info_lines(build_id, cloud_project='oss-fuzz'):
"""Returns a list of strings with build information."""
gcb_url = get_gcb_url(build_id, cloud_project)
log_url = get_logs_url(build_id)
return [
f'GCB Build ID: {build_id}',
f'GCB Build URL: {gcb_url}',
f'Log URL: {log_url}',
]


def get_runner_image_name(test_image_suffix, base_image_tag=None):
"""Returns the runner image that should be used.
Expand Down Expand Up @@ -732,6 +738,7 @@ def get_build_body( # pylint: disable=too-many-arguments
'steps': steps,
'timeout': str(timeout) + 's',
'options': options,
'logsBucket': 'gs://oss-fuzz-gcb-logs',
}
if tags:
build_body['tags'] = tags
Expand Down
3 changes: 3 additions & 0 deletions infra/build/functions/gcb.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ def exec_command_from_github(args):

if full_command is None:
logging.info('Trial build not requested.')
# Create a flag file to indicate that the build was skipped.
with open('trial_build_skipped.flag', 'w') as f:
pass
return None
command_file = full_command[0]
command = full_command[1:]
Expand Down
4 changes: 4 additions & 0 deletions infra/build/functions/report_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ def generate_comparison_table(all_results):

def main():
"""Main function to generate report and determine pipeline status."""
if os.path.exists('trial_build_skipped.flag'):
print('Skipping report generation because trial build was not invoked.')
sys.exit(0)

all_results = {}
any_failures = False
any_results_found = False
Expand Down
41 changes: 24 additions & 17 deletions infra/build/functions/trial_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,14 +362,15 @@ def _do_test_builds(args, test_image_suffix, end_time, version_tag):
for project, project_builds in sorted(build_ids.items()):
logging.info(' - %s:', project)
for build_id, build_type in project_builds:
logging.info(' - Build ID: %s', build_id)
logging.info(' - Build Type: %s', build_type)
logging.info(' GCB URL: %s',
build_lib.get_gcb_url(build_id, build_lib.IMAGE_PROJECT))
for line in build_lib.get_build_info_lines(build_id,
build_lib.IMAGE_PROJECT):
logging.info(' %s', line)
logging.info('-----------------------')

wait_result = wait_on_builds(build_ids, credentials, build_lib.IMAGE_PROJECT,
end_time, skipped_projects, version_tag)
wait_result = wait_on_builds(args, build_ids, credentials,
build_lib.IMAGE_PROJECT, end_time,
skipped_projects, version_tag)

if failed_to_start_builds:
logging.error(
Expand Down Expand Up @@ -483,7 +484,7 @@ def check_finished(build_id, cloudbuild_api, cloud_project, retries_map):
return build_status


def wait_on_builds(build_ids, credentials, cloud_project, end_time,
def wait_on_builds(args, build_ids, credentials, cloud_project, end_time,
skipped_projects, version_tag): # pylint: disable=too-many-locals
"""Waits on |builds|. Returns True if all builds succeed."""
cloudbuild = cloud_build('cloudbuild',
Expand Down Expand Up @@ -544,22 +545,27 @@ def wait_on_builds(build_ids, credentials, cloud_project, end_time,
if status == 'SUCCESS':
successful_builds[project].append(build_id)
else:
logs_url = build_lib.get_gcb_url(build_id, cloud_project)
failed_builds[project].append((status, logs_url, build_type))
gcb_url = build_lib.get_gcb_url(build_id, cloud_project)
log_url = build_lib.get_logs_url(build_id)
failed_builds[project].append(
(status, gcb_url, build_type, log_url))

wait_builds[project].remove((build_id, build_type))
if not wait_builds[project]:
del wait_builds[project]

elif retries_map.get(build_id, 0) >= MAX_RETRIES:
# Max retries reached, mark as failed.
logging.error('HttpError for build %s. Max retries reached.',
build_id)
if build_id in next_retry_time:
del next_retry_time[build_id]

finished_builds_count += 1
status = 'UNKNOWN (too many HttpErrors)'
logs_url = build_lib.get_gcb_url(build_id, cloud_project)
failed_builds[project].append((status, logs_url, build_type))
gcb_url = build_lib.get_gcb_url(build_id, cloud_project)
log_url = build_lib.get_logs_url(build_id)
failed_builds[project].append((status, gcb_url, build_type, log_url))
wait_builds[project].remove((build_id, build_type))
if not wait_builds[project]:
del wait_builds[project]
Expand All @@ -570,8 +576,6 @@ def wait_on_builds(build_ids, credentials, cloud_project, end_time,
random.uniform(0, 1))
next_retry_time[build_id] = (datetime.datetime.now() +
datetime.timedelta(seconds=backoff_time))
logging.warning('HttpError for build %s. Retrying in %.2f seconds.',
build_id, backoff_time)

if not processed_a_build_in_iteration and wait_builds:
# All remaining builds are in backoff, sleep to prevent busy-waiting.
Expand All @@ -584,9 +588,10 @@ def wait_on_builds(build_ids, credentials, cloud_project, end_time,
if wait_builds:
for project, project_builds in list(wait_builds.items()):
for build_id, build_type in project_builds:
logs_url = build_lib.get_gcb_url(build_id, cloud_project)
gcb_url = build_lib.get_gcb_url(build_id, cloud_project)
log_url = build_lib.get_logs_url(build_id)
failed_builds[project].append(
('TIMEOUT (Coordinator)', logs_url, build_type))
('TIMEOUT (Coordinator)', gcb_url, build_type, log_url))

# Final Report
successful_builds_count = sum(
Expand Down Expand Up @@ -634,17 +639,19 @@ def wait_on_builds(build_ids, credentials, cloud_project, end_time,
logging.error('--- FAILED BUILDS ---')
for project, failures in sorted(failed_builds.items()):
logging.error(' - %s:', project)
for status, gcb_url, build_type in failures:
for status, gcb_url, build_type, log_url in failures:
build_id = gcb_url.split('/')[-1].split('?')[0]
logging.error(' - Build ID: %s', build_id)
logging.error(' - Build Type: %s', build_type)
logging.error(' - Status: %s', status)
logging.error(' - GCB URL: %s', gcb_url)
for line in build_lib.get_build_info_lines(build_id, cloud_project):
logging.error(' - %s', line)
logging.info('-----------------------')
return False

if not finished_builds_count and not skipped_builds_count:
logging.warning('No builds were run.')
if args.skip_build_images:
return True
return False

logging.info('\nAll builds passed successfully!')
Expand Down
Loading