Support postsubmit checkruns for plugins/packages (#2254)
diff --git a/app_dart/bin/server.dart b/app_dart/bin/server.dart
index 36b641f..d0dbc37 100644
--- a/app_dart/bin/server.dart
+++ b/app_dart/bin/server.dart
@@ -103,6 +103,7 @@
config: config,
luciBuildService: luciBuildService,
scheduler: scheduler,
+ githubChecksService: githubChecksService,
),
'/api/push-build-status-to-github': PushBuildStatusToGithub(
config: config,
diff --git a/app_dart/lib/src/request_handlers/postsubmit_luci_subscription.dart b/app_dart/lib/src/request_handlers/postsubmit_luci_subscription.dart
index 6be1cd4..7db236a 100644
--- a/app_dart/lib/src/request_handlers/postsubmit_luci_subscription.dart
+++ b/app_dart/lib/src/request_handlers/postsubmit_luci_subscription.dart
@@ -7,6 +7,7 @@
import 'package:cocoon_service/ci_yaml.dart';
import 'package:cocoon_service/src/service/luci_build_service.dart';
import 'package:gcloud/db.dart';
+import 'package:github/github.dart';
import 'package:meta/meta.dart';
import '../model/appengine/commit.dart';
@@ -17,6 +18,7 @@
import '../request_handling/subscription_handler.dart';
import '../service/datastore.dart';
import '../service/logging.dart';
+import '../service/github_checks_service.dart';
import '../service/scheduler.dart';
/// An endpoint for listening to build updates for postsubmit builds.
@@ -35,11 +37,13 @@
@visibleForTesting this.datastoreProvider = DatastoreService.defaultProvider,
required this.luciBuildService,
required this.scheduler,
+ required this.githubChecksService,
}) : super(subscriptionName: 'luci-postsubmit');
final DatastoreServiceProvider datastoreProvider;
final LuciBuildService luciBuildService;
final Scheduler scheduler;
+ final GithubChecksService githubChecksService;
@override
Future<Body> post() async {
@@ -71,6 +75,22 @@
userData = jsonDecode(String.fromCharCodes(base64.decode(buildPushMessage.userData!))) as Map<String, dynamic>;
}
+ if (userData.containsKey('repo_owner') && userData.containsKey('repo_name')) {
+ // Message is coming from a github checks api (postsubmit) enabled repo. We need to
+ // create the slug from the data in the message and send the check status
+ // update.
+
+ RepositorySlug slug = RepositorySlug(
+ userData['repo_owner'] as String,
+ userData['repo_name'] as String,
+ );
+ await githubChecksService.updateCheckStatus(
+ buildPushMessage,
+ luciBuildService,
+ slug,
+ );
+ }
+
final String? rawTaskKey = userData['task_key'] as String?;
final String? rawCommitKey = userData['commit_key'] as String?;
if (rawCommitKey == null) {
diff --git a/app_dart/lib/src/service/config.dart b/app_dart/lib/src/service/config.dart
index 5960e96..5b1a89f 100644
--- a/app_dart/lib/src/service/config.dart
+++ b/app_dart/lib/src/service/config.dart
@@ -49,6 +49,14 @@
pluginsSlug,
};
+ /// List of Github postsubmit supported repos.
+ ///
+ /// This adds support for check runs to the repo.
+ Set<gh.RepositorySlug> get postsubmitSupportedRepos => <gh.RepositorySlug>{
+ packagesSlug,
+ pluginsSlug,
+ };
+
/// List of Cirrus supported repos.
static Set<String> cirrusSupportedRepos = <String>{'plugins', 'packages', 'flutter'};
@@ -417,4 +425,8 @@
bool githubPresubmitSupportedRepo(gh.RepositorySlug slug) {
return supportedRepos.contains(slug);
}
+
+ bool githubPostsubmitSupportedRepo(gh.RepositorySlug slug) {
+ return postsubmitSupportedRepos.contains(slug);
+ }
}
diff --git a/app_dart/lib/src/service/github_checks_service.dart b/app_dart/lib/src/service/github_checks_service.dart
index 18c83f1..42f48fa 100644
--- a/app_dart/lib/src/service/github_checks_service.dart
+++ b/app_dart/lib/src/service/github_checks_service.dart
@@ -101,7 +101,7 @@
// If status has completed with failure then provide more details.
if (status == github.CheckRunStatus.completed && failedStatesSet.contains(conclusion)) {
final Build build =
- await luciBuildService.getTryBuildById(buildPushMessage.build!.id, fields: 'id,builder,summaryMarkdown');
+ await luciBuildService.getBuildById(buildPushMessage.build!.id, fields: 'id,builder,summaryMarkdown');
output = github.CheckRunOutput(title: checkRun.name!, summary: getGithubSummary(build.summaryMarkdown));
}
log.fine('Updating check run with output: [$output]');
diff --git a/app_dart/lib/src/service/luci_build_service.dart b/app_dart/lib/src/service/luci_build_service.dart
index 3af821d..bcb0d96 100644
--- a/app_dart/lib/src/service/luci_build_service.dart
+++ b/app_dart/lib/src/service/luci_build_service.dart
@@ -390,7 +390,7 @@
/// Gets [Build] using its [id] and passing the additional
/// fields to be populated in the response.
- Future<Build> getTryBuildById(String? id, {String? fields}) async {
+ Future<Build> getBuildById(String? id, {String? fields}) async {
final GetBuildRequest request = GetBuildRequest(id: id, fields: fields);
return buildBucketClient.getBuild(request);
}
@@ -429,7 +429,7 @@
if (!availableBuilderSet.contains(tuple.first.value.name)) {
continue;
}
- final ScheduleBuildRequest scheduleBuildRequest = _createPostsubmitScheduleBuild(
+ final ScheduleBuildRequest scheduleBuildRequest = await _createPostsubmitScheduleBuild(
commit: commit,
target: tuple.first,
task: tuple.second,
@@ -499,14 +499,14 @@
/// Creates a [ScheduleBuildRequest] for [target] and [task] against [commit].
///
/// By default, build [priority] is increased for release branches.
- ScheduleBuildRequest _createPostsubmitScheduleBuild({
+ Future<ScheduleBuildRequest> _createPostsubmitScheduleBuild({
required Commit commit,
required Target target,
required Task task,
Map<String, Object>? properties,
Map<String, List<String>>? tags,
int priority = kDefaultPriority,
- }) {
+ }) async {
tags ??= <String, List<String>>{};
tags.addAll(<String, List<String>>{
'buildset': <String>[
@@ -521,10 +521,13 @@
log.info('Task commit_key: $commitKey for task name: ${task.name}');
log.info('Task task_key: $taskKey for task name: ${task.name}');
- final Map<String, String> rawUserData = <String, String>{
+ final Map<String, dynamic> rawUserData = <String, dynamic>{
'commit_key': commitKey,
'task_key': taskKey,
};
+
+ await createPostsubmitCheckRun(commit, target, rawUserData);
+
tags['user_agent'] = <String>['flutter-cocoon'];
// Tag `scheduler_job_id` is needed when calling buildbucket search build API.
tags['scheduler_job_id'] = <String>['flutter/${target.value.name}'];
@@ -559,6 +562,29 @@
);
}
+ /// Creates postsubmit check runs for supported repositories.
+ Future<void> createPostsubmitCheckRun(
+ Commit commit,
+ Target target,
+ Map<String, dynamic> rawUserData,
+ ) async {
+ if (!config.githubPostsubmitSupportedRepo(commit.slug)) {
+ return;
+ }
+ final github.CheckRun checkRun = await githubChecksUtil.createCheckRun(
+ config,
+ target.slug,
+ commit.sha!,
+ target.value.name,
+ );
+ rawUserData['check_run_id'] = checkRun.id;
+ rawUserData['commit_sha'] = commit.sha;
+ rawUserData['commit_branch'] = commit.branch;
+ rawUserData['builder_name'] = target.value.name;
+ rawUserData['repo_owner'] = target.slug.owner;
+ rawUserData['repo_name'] = target.slug.name;
+ }
+
/// Check to auto-rerun TOT test failures.
///
/// A builder will be retried if:
@@ -584,7 +610,7 @@
final BatchRequest request = BatchRequest(
requests: <Request>[
Request(
- scheduleBuild: _createPostsubmitScheduleBuild(
+ scheduleBuild: await _createPostsubmitScheduleBuild(
commit: commit,
target: target,
task: task,
diff --git a/app_dart/test/request_handlers/postsubmit_luci_subscription_test.dart b/app_dart/test/request_handlers/postsubmit_luci_subscription_test.dart
index b87d5e0..84c5c8d 100644
--- a/app_dart/test/request_handlers/postsubmit_luci_subscription_test.dart
+++ b/app_dart/test/request_handlers/postsubmit_luci_subscription_test.dart
@@ -8,6 +8,7 @@
import 'package:cocoon_service/src/request_handling/exceptions.dart';
import 'package:cocoon_service/src/service/datastore.dart';
import 'package:gcloud/db.dart';
+import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import '../src/datastore/fake_config.dart';
@@ -17,6 +18,7 @@
import '../src/service/fake_luci_build_service.dart';
import '../src/service/fake_scheduler.dart';
import '../src/utilities/entity_generators.dart';
+import '../src/utilities/mocks.dart';
import '../src/utilities/push_message.dart';
void main() {
@@ -24,13 +26,16 @@
late FakeConfig config;
late FakeHttpRequest request;
late SubscriptionTester tester;
+ late MockGithubChecksService mockGithubChecksService;
setUp(() async {
config = FakeConfig(maxLuciTaskRetriesValue: 3);
+ mockGithubChecksService = MockGithubChecksService();
handler = PostsubmitLuciSubscription(
cache: CacheService(inMemory: true),
config: config,
authProvider: FakeAuthenticationProvider(),
+ githubChecksService: mockGithubChecksService,
datastoreProvider: (_) => DatastoreService(config.db, 5),
luciBuildService: FakeLuciBuildService(config: config),
scheduler: FakeScheduler(
@@ -145,4 +150,25 @@
expect(await tester.post(handler), Body.empty);
expect(task.status, Task.statusInProgress);
});
+
+ test('Requests with repo_owner and repo_name update checks', () async {
+ when(mockGithubChecksService.updateCheckStatus(any, any, any)).thenAnswer((_) async => true);
+ final Commit commit = generateCommit(1, sha: '87f88734747805589f2131753620d61b22922822');
+ final Task task = generateTask(
+ 4507531199512576,
+ parent: commit,
+ );
+ config.db.values[task.key] = task;
+
+ tester.message = createBuildbucketPushMessage(
+ 'COMPLETED',
+ result: 'SUCCESS',
+ builderName: 'Linux Packages',
+ // Use escaped string to mock json decoded ones.
+ userData:
+ '{\\"task_key\\":\\"${task.key.id}\\", \\"commit_key\\":\\"${task.key.parent?.id}\\", \\"repo_owner\\": \\"flutter\\", \\"repo_name\\": \\"packages\\"}',
+ );
+ await tester.post(handler);
+ verify(mockGithubChecksService.updateCheckStatus(any, any, any)).called(1);
+ });
}
diff --git a/app_dart/test/service/luci_build_service_test.dart b/app_dart/test/service/luci_build_service_test.dart
index 3313adc..75d80d4 100644
--- a/app_dart/test/service/luci_build_service_test.dart
+++ b/app_dart/test/service/luci_build_service_test.dart
@@ -408,6 +408,7 @@
service = LuciBuildService(
config: FakeConfig(),
buildBucketClient: mockBuildBucketClient,
+ githubChecksUtil: mockGithubChecksUtil,
pubsub: pubsub,
);
});
@@ -462,6 +463,52 @@
'debian-10.12');
});
+ test('schedule postsubmit builds with correct userData for checkRuns', () async {
+ when(mockGithubChecksUtil.createCheckRun(any, any, any, any))
+ .thenAnswer((_) async => generateCheckRun(1, name: 'Linux 1'));
+ final Commit commit = generateCommit(0, repo: 'packages');
+ when(mockBuildBucketClient.listBuilders(any)).thenAnswer((_) async {
+ return const ListBuildersResponse(builders: [
+ BuilderItem(id: BuilderId(bucket: 'prod', project: 'flutter', builder: 'Linux 1')),
+ ]);
+ });
+ final Tuple<Target, Task, int> toBeScheduled = Tuple<Target, Task, int>(
+ generateTarget(1,
+ properties: <String, String>{
+ 'os': 'debian-10.12',
+ },
+ slug: RepositorySlug('flutter', 'packages')),
+ generateTask(1),
+ LuciBuildService.kDefaultPriority,
+ );
+ await service.schedulePostsubmitBuilds(
+ commit: commit,
+ toBeScheduled: <Tuple<Target, Task, int>>[
+ toBeScheduled,
+ ],
+ );
+ // Only one batch request should be published
+ expect(pubsub.messages.length, 1);
+ final BatchRequest request = pubsub.messages.first as BatchRequest;
+ expect(request.requests?.single.scheduleBuild, isNotNull);
+ final ScheduleBuildRequest scheduleBuild = request.requests!.single.scheduleBuild!;
+ expect(scheduleBuild.builderId.bucket, 'prod');
+ expect(scheduleBuild.builderId.builder, 'Linux 1');
+ expect(scheduleBuild.notify?.pubsubTopic, 'projects/flutter-dashboard/topics/luci-builds-prod');
+ final Map<String, dynamic> userData =
+ jsonDecode(String.fromCharCodes(base64Decode(scheduleBuild.notify!.userData!))) as Map<String, dynamic>;
+ expect(userData, <String, dynamic>{
+ 'commit_key': 'flutter/flutter/master/1',
+ 'task_key': '1',
+ 'check_run_id': 1,
+ 'commit_sha': '0',
+ 'commit_branch': 'master',
+ 'builder_name': 'Linux 1',
+ 'repo_owner': 'flutter',
+ 'repo_name': 'packages'
+ });
+ });
+
test('Skip non-existing builder', () async {
final Commit commit = generateCommit(0);
when(mockBuildBucketClient.listBuilders(any)).thenAnswer((_) async {
diff --git a/app_dart/test/src/datastore/fake_config.dart b/app_dart/test/src/datastore/fake_config.dart
index 2116840..0400751 100644
--- a/app_dart/test/src/datastore/fake_config.dart
+++ b/app_dart/test/src/datastore/fake_config.dart
@@ -52,6 +52,7 @@
this.flutterGoldFollowUpAlertValue,
this.flutterGoldDraftChangeValue,
this.flutterGoldStalePRValue,
+ this.postsubmitSupportedReposValue,
this.supportedBranchesValue,
this.supportedReposValue,
this.batchSizeValue,
@@ -96,6 +97,7 @@
List<String>? supportedBranchesValue;
String? overrideTreeStatusLabelValue;
Set<gh.RepositorySlug>? supportedReposValue;
+ Set<gh.RepositorySlug>? postsubmitSupportedReposValue;
Duration? githubRequestDelayValue;
@override
@@ -231,6 +233,14 @@
}
@override
+ bool githubPostsubmitSupportedRepo(gh.RepositorySlug slug) {
+ return <gh.RepositorySlug>[
+ Config.packagesSlug,
+ Config.pluginsSlug,
+ ].contains(slug);
+ }
+
+ @override
Future<String> generateGithubToken(gh.RepositorySlug slug) {
throw UnimplementedError();
}
@@ -268,6 +278,10 @@
Set<gh.RepositorySlug> get supportedRepos => supportedReposValue ?? <gh.RepositorySlug>{Config.flutterSlug};
@override
+ Set<gh.RepositorySlug> get postsubmitSupportedRepos =>
+ postsubmitSupportedReposValue ?? <gh.RepositorySlug>{Config.packagesSlug};
+
+ @override
Future<Iterable<Branch>> getBranches(gh.RepositorySlug slug) async {
if (supportedBranchesValue == null) {
throw Exception('Test must set suportedBranchesValue to be able to use Config.getBranches');