blob: 35a12e5475b352acf413beb78d3de21f8b946e1d [file] [log] [blame]
// Copyright 2023 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// import 'package:cocoon_service/src/model/luci/buildbucket.dart';
import 'dart:convert';
import 'package:buildbucket/buildbucket_pb.dart' as bbv2;
import 'package:fixnum/fixnum.dart';
import 'package:googleapis/firestore/v1.dart';
import 'package:meta/meta.dart';
import '../../cocoon_service.dart';
import '../model/appengine/task.dart';
import '../model/firestore/task.dart' as firestore;
import '../request_handling/subscription_handler_v2.dart';
import '../service/build_bucket_v2_client.dart';
import '../service/datastore.dart';
import '../service/logging.dart';
/// TODO(drewroengoogle): Make this subscription generic so we can accept more
/// than just dart-internal builds.
///
/// An endpoint for listening to build updates for dart-internal builds and
/// saving the results to the datastore.
///
/// The PubSub subscription is set up here:
/// https://console.cloud.google.com/cloudpubsub/subscription/detail/dart-internal-build-results-sub?project=flutter-dashboard
@immutable
class DartInternalSubscription extends SubscriptionHandlerV2 {
/// Creates an endpoint for listening for dart-internal build results.
/// The message should contain a single buildbucket id
const DartInternalSubscription({
required super.cache,
required super.config,
super.authProvider,
required this.buildBucketV2Client,
@visibleForTesting this.datastoreProvider = DatastoreService.defaultProvider,
}) : super(subscriptionName: 'dart-internal-build-results-sub');
final BuildBucketV2Client buildBucketV2Client;
final DatastoreServiceProvider datastoreProvider;
@override
Future<Body> post() async {
final DatastoreService datastore = datastoreProvider(config.db);
if (message.data == null) {
log.info('no data in message');
return Body.empty;
}
// This looks to be like we are simply getting the build and not the top level
// buildsPubSub message.
final Map<String, dynamic> jsonBuildMap = json.decode(message.data!);
if (jsonBuildMap['build'] == null) {
log.info('no build information in message');
return Body.empty;
}
final String project = jsonBuildMap['build']['builder']['project'];
final String bucket = jsonBuildMap['build']['builder']['bucket'];
final String builder = jsonBuildMap['build']['builder']['builder'];
final Int64 buildId = Int64.parseInt(jsonBuildMap['build']['id']);
// This should already be covered by the pubsub filter, but adding an additional check
// to ensure we don't process builds that aren't from dart-internal/flutter.
if (project != 'dart-internal' || bucket != 'flutter') {
log.info('Ignoring build not from dart-internal/flutter bucket');
return Body.empty;
}
// Only publish the parent release_builder builds to the datastore.
// TODO(drewroengoogle): Remove this regex in favor of supporting *all* dart-internal build results.
// Issue: https://github.com/flutter/flutter/issues/134674
final regex =
RegExp(r'(Linux|Mac|Windows)\s+(engine_release_builder|packaging_release_builder|flutter_release_builder)');
if (!regex.hasMatch(builder)) {
log.info('Ignoring builder that is not a release builder');
return Body.empty;
}
log.info('Creating build request object with build id $buildId');
final bbv2.GetBuildRequest getBuildRequest = bbv2.GetBuildRequest(
id: buildId,
);
log.info(
'Calling buildbucket api to get build data for build $buildId',
);
final bbv2.Build existingBuild = await buildBucketV2Client.getBuild(getBuildRequest);
log.info('Got back existing builder with name: ${existingBuild.builder.builder}');
log.info('Checking for existing task in datastore');
final Task? existingTask = await datastore.getTaskFromBuildbucketV2Build(existingBuild);
late Task taskToInsert;
if (existingTask != null) {
log.info('Updating Task from existing Build');
existingTask.updateFromBuildbucketV2Build(existingBuild);
taskToInsert = existingTask;
} else {
log.info('Creating Task from Buildbucket result');
taskToInsert = await Task.fromBuildbucketV2Build(existingBuild, datastore);
}
log.info('Inserting Task into the datastore: ${taskToInsert.toString()}');
await datastore.insert(<Task>[taskToInsert]);
try {
final FirestoreService firestoreService = await config.createFirestoreService();
final firestore.Task taskDocument = firestore.taskToDocument(taskToInsert);
final List<Write> writes = documentsToWrites([taskDocument]);
await firestoreService.batchWriteDocuments(BatchWriteRequest(writes: writes), kDatabase);
} catch (error) {
log.warning('Failed to insert dart internal task into firestore: $error');
}
return Body.forJson(taskToInsert.toString());
}
}