blob: 80cda81fb0bf1efa62b9cdba4e0c432c29a54167 [file] [log] [blame]
// Copyright 2022 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.
// Note that we need this file because Github does not expose a field within the
// checks that states whether or not a particular check is required or not.
import 'package:auto_submit/configuration/repository_configuration.dart';
import 'package:auto_submit/exception/retryable_exception.dart';
import 'package:auto_submit/model/auto_submit_query_result.dart' as auto;
import 'package:auto_submit/service/config.dart';
import 'package:auto_submit/service/github_service.dart';
import 'package:auto_submit/service/log.dart';
import 'package:auto_submit/validations/validation.dart';
import 'package:github/github.dart' as github;
import 'package:retry/retry.dart';
const String ciyamlValidation = 'ci.yaml validation';
/// Required check runs are check runs noted in the autosubmit.yaml configuration.
/// In order for a pull request to be merged any check runs specified in the
/// required check runs must pass before the bot will merge the pull request
/// regardless of review status.
class RequiredCheckRuns extends Validation {
const RequiredCheckRuns({
required super.config,
RetryOptions? retryOptions,
}) : retryOptions = retryOptions ?? Config.requiredChecksRetryOptions;
final RetryOptions retryOptions;
Future<bool> waitForRequiredChecks({
required github.RepositorySlug slug,
required String sha,
required Set<String> checkNames,
}) async {
final GithubService githubService = await config.createGithubService(slug);
final List<github.CheckRun> targetCheckRuns = [];
for (String checkRun in checkNames) {
targetCheckRuns.addAll(
await githubService.getCheckRunsFiltered(
slug: slug,
ref: sha,
checkName: checkRun,
),
);
}
bool checksCompleted = true;
try {
for (github.CheckRun checkRun in targetCheckRuns) {
await retryOptions.retry(
() async {
await _verifyCheckRunCompleted(
slug,
githubService,
checkRun,
);
},
retryIf: (Exception e) => e is RetryableException,
);
}
} catch (e) {
log.warning('Required check has not completed in time. ${e.toString()}');
checksCompleted = false;
}
return checksCompleted;
}
@override
String get name => 'RequiredCheckRuns';
@override
Future<ValidationResult> validate(auto.QueryResult result, github.PullRequest messagePullRequest) async {
final auto.PullRequest pullRequest = result.repository!.pullRequest!;
final auto.Commit commit = pullRequest.commits!.nodes!.single.commit!;
final String? sha = commit.oid;
final github.RepositorySlug slug = messagePullRequest.base!.repo!.slug();
final RepositoryConfiguration repositoryConfiguration = await config.getRepositoryConfiguration(slug);
final Set<String> requiredCheckRuns = repositoryConfiguration.requiredCheckRunsOnRevert;
final bool success = await waitForRequiredChecks(slug: slug, sha: sha!, checkNames: requiredCheckRuns);
return ValidationResult(
success,
success ? Action.REMOVE_LABEL : Action.IGNORE_TEMPORARILY,
success ? 'All required check runs have completed.' : 'Some of the required checks did not complete in time.',
);
}
}
/// Function signature that will be executed with retries.
typedef RetryHandler = Function();
/// Simple function to wait on completed checkRuns with retries.
Future<void> _verifyCheckRunCompleted(
github.RepositorySlug slug,
GithubService githubService,
github.CheckRun targetCheckRun,
) async {
final List<github.CheckRun> checkRuns = await githubService.getCheckRunsFiltered(
slug: slug,
ref: targetCheckRun.headSha!,
checkName: targetCheckRun.name,
);
if (checkRuns.first.name != targetCheckRun.name || checkRuns.first.conclusion != github.CheckRunConclusion.success) {
throw RetryableException('${targetCheckRun.name} has not yet completed.');
}
}