blob: e3e8b05c78fbff9c323303f4f865e65b13e97db9 [file] [log] [blame]
// Copyright 2019 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 'dart:io';
import 'dart:math';
import 'package:appengine/appengine.dart';
import 'package:cocoon_service/cocoon_service.dart';
import 'package:gcloud/db.dart';
/// For local development, you might want to set this to true.
const String _kCocoonUseInMemoryCache = 'COCOON_USE_IN_MEMORY_CACHE';
Future<void> main() async {
await withAppEngineServices(() async {
useLoggingPackageAdaptor();
final bool inMemoryCache = Platform.environment[_kCocoonUseInMemoryCache] == 'true';
final CacheService cache = CacheService(inMemory: inMemoryCache);
final Config config = Config(dbService, cache);
final AuthenticationProvider authProvider = AuthenticationProvider(config: config);
final AuthenticationProvider swarmingAuthProvider = SwarmingAuthenticationProvider(config: config);
final BuildBucketClient buildBucketClient = BuildBucketClient(
accessTokenService: AccessTokenService.defaultProvider(config),
);
/// LUCI service class to communicate with buildBucket service.
final LuciBuildService luciBuildService = LuciBuildService(
config: config,
buildBucketClient: buildBucketClient,
pubsub: const PubSub(),
);
/// Github checks api service used to provide luci test execution status on the Github UI.
final GithubChecksService githubChecksService = GithubChecksService(
config,
);
/// Cocoon scheduler service to manage validating commits in presubmit and postsubmit.
final Scheduler scheduler = Scheduler(
cache: cache,
config: config,
githubChecksService: githubChecksService,
luciBuildService: luciBuildService,
);
final BranchService branchService = BranchService(
config: config,
gerritService: GerritService(),
);
final Map<String, RequestHandler<dynamic>> handlers = <String, RequestHandler<dynamic>>{
'/api/check_flaky_builders': CheckFlakyBuilders(
config: config,
authenticationProvider: authProvider,
),
'/api/file_flaky_issue_and_pr': FileFlakyIssueAndPR(
config: config,
authenticationProvider: authProvider,
),
'/api/flush-cache': FlushCache(
config: config,
authenticationProvider: authProvider,
cache: cache,
),
'/api/get-authentication-status': GetAuthenticationStatus(
config: config,
authenticationProvider: authProvider,
),
'/api/github-webhook-pullrequest': GithubWebhook(
config: config,
pubsub: const PubSub(),
),
'/api/github/webhook-subscription': GithubWebhookSubscription(
config: config,
cache: cache,
branchService: branchService,
githubChecksService: githubChecksService,
scheduler: scheduler,
),
/// API to run authenticated graphql queries. It requires to pass the graphql query as the body
/// of a POST request.
'/api/query-github-graphql': QueryGithubGraphql(
config: config,
authenticationProvider: authProvider,
),
'/api/presubmit-luci-subscription': PresubmitLuciSubscription(
cache: cache,
config: config,
buildBucketClient: buildBucketClient,
luciBuildService: luciBuildService,
githubChecksService: githubChecksService,
),
'/api/postsubmit-luci-subscription': PostsubmitLuciSubscription(
cache: cache,
config: config,
luciBuildService: luciBuildService,
scheduler: scheduler,
),
'/api/push-build-status-to-github': PushBuildStatusToGithub(
config: config,
authenticationProvider: authProvider,
),
'/api/push-gold-status-to-github': PushGoldStatusToGithub(
config: config,
authenticationProvider: authProvider,
),
'/api/reset-prod-task': ResetProdTask(
config: config,
authenticationProvider: authProvider,
luciBuildService: luciBuildService,
scheduler: scheduler,
),
'/api/reset-try-task': ResetTryTask(
config: config,
authenticationProvider: authProvider,
scheduler: scheduler,
),
'/api/scheduler/batch-backfiller': BatchBackfiller(
config: config,
scheduler: scheduler,
),
'/api/scheduler/batch-request-subscription': SchedulerRequestSubscription(
cache: cache,
config: config,
buildBucketClient: buildBucketClient,
),
'/api/update_existing_flaky_issues': UpdateExistingFlakyIssue(
config: config,
authenticationProvider: authProvider,
),
'/api/update-branches': UpdateBranches(
config: config,
),
/// Updates task related details.
///
/// This API updates task status in datastore and
/// pushes performance metrics to skia-perf.
///
/// POST: /api-update-status
///
/// Parameters:
/// CommitBranch: (string in body). Branch of commit.
/// CommitSha: (string in body). Sha of commit.
/// BuilderName: (string in body). Name of the luci builder.
/// NewStatus: (string in body) required. Status of the task.
/// ResultData: (string in body) optional. Benchmark data.
/// BenchmarkScoreKeys: (string in body) optional. Benchmark data.
///
/// Response: Status 200 OK
'/api/update-task-status': UpdateTaskStatus(
config: config,
authenticationProvider: swarmingAuthProvider,
),
'/api/vacuum-github-commits': VacuumGithubCommits(
config: config,
authenticationProvider: authProvider,
scheduler: scheduler,
),
/// Returns status of the framework tree.
///
/// Returns serialized proto with enum representing the
/// status of the tree and list of offending tasks.
///
/// GET: /api/public/build-status
///
/// Parameters:
/// branch: (string in query) default: 'master'. Name of the repo branch.
///
/// Response: Status 200 OK
/// Returns [BuildStatusResponse]:
/// {
/// 1: 2,
/// 2: [ "win_tool_tests_commands", "win_build_test", "win_module_test"]
/// }
'/api/public/build-status': CacheRequestHandler<Body>(
cache: cache,
config: config,
delegate: GetBuildStatus(config: config),
ttl: const Duration(seconds: 15),
),
'/api/public/get-release-branches': GetReleaseBranches(config: config, branchService: branchService),
/// Returns task results for commits.
///
/// Returns result details about each task in each checklist for every commit.
///
/// GET: /api/public/get-status
///
/// Parameters:
/// branch: (string in query) default: 'master'. Name of the repo branch.
/// lastCommitKey: (string in query) optional. Encoded commit key for the last commit to return resutls.
///
/// Response: Status: 200 OK
/// {"Statuses":[
/// {"Checklist":{
/// "Key":"ah..jgM",
/// "Checklist":{"FlutterRepositoryPath":"flutter/flutter",
/// "CreateTimestamp":1620134239000,
/// "Commit":{"Sha":"7f1d1414cc5f0b0317272ced49a9c0b44e5c3af8",
/// "Message":"Revert \"Migrate to ChannelBuffers.push\"",
/// "Author":{"Login":"renyou","avatar_url":"https://avatars.githubusercontent.com/u/666474?v=4"}},"Branch":"master"}},
/// "Stages":[{"Name":"chromebot",
/// "Tasks":[
/// {"Task":{
/// "ChecklistKey":"ahF..jgM",
/// "CreateTimestamp":1620134239000,
/// "StartTimestamp":0,
/// "EndTimestamp":1620136203757,
/// "Name":"linux_cubic_bezier_perf__e2e_summary",
/// "Attempts":1,
/// "Flaky":false,
/// "TimeoutInMinutes":0,
/// "Reason":"",
/// "BuildNumber":null,
/// "BuildNumberList":"1279",
/// "BuilderName":"Linux cubic_bezier_perf__e2e_summary",
/// "luciBucket":"luci.flutter.prod",
/// "RequiredCapabilities":["can-update-github"],
/// "ReservedForAgentID":"",
/// "StageName":"chromebot",
/// "Status":"Succeeded"
/// },
/// ],
/// "Status": "InProgress",
/// ]},
/// },
/// }
'/api/public/get-status': CacheRequestHandler<Body>(
cache: cache,
config: config,
delegate: GetStatus(config: config),
),
'/api/public/get-green-commits': GetGreenCommits(config: config),
'/api/public/get-branches': CacheRequestHandler<Body>(
cache: cache,
config: config,
delegate: GetBranches(config: config),
ttl: const Duration(minutes: 15),
),
/// Record GitHub API quota usage in BigQuery.
///
/// Pushes data to BigQuery for metric collection to
/// analyze usage over time.
///
/// This api is called via cron job.
///
/// GET: /api/public/github-rate-limit-status
///
/// Response: Status 200 OK
'/api/public/github-rate-limit-status': CacheRequestHandler<Body>(
config: config,
cache: cache,
ttl: const Duration(minutes: 1),
delegate: GithubRateLimitStatus(config: config),
),
'/api/public/repos': GetRepos(config: config),
/// Handler for AppEngine to identify when dart server is ready to serve requests.
'/readiness_check': ReadinessCheck(config: config),
};
return await runAppEngine((HttpRequest request) async {
if (handlers.containsKey(request.uri.path)) {
final RequestHandler<dynamic> handler = handlers[request.uri.path]!;
await handler.service(request);
} else {
/// Requests with query parameters and anchors need to be trimmed to get the file path.
// TODO(chillers): Use toFilePath(), https://github.com/dart-lang/sdk/issues/39373
final int queryIndex = request.uri.path.contains('?') ? request.uri.path.indexOf('?') : request.uri.path.length;
final int anchorIndex =
request.uri.path.contains('#') ? request.uri.path.indexOf('#') : request.uri.path.length;
/// Trim to the first instance of an anchor or query.
final int trimIndex = min(queryIndex, anchorIndex);
final String filePath = request.uri.path.substring(0, trimIndex);
const Map<String, String> redirects = <String, String>{
'/build.html': '/#/build',
};
if (redirects.containsKey(filePath)) {
request.response.statusCode = HttpStatus.permanentRedirect;
return await request.response.redirect(Uri.parse(redirects[filePath]!));
}
await StaticFileHandler(filePath, config: config).service(request);
}
}, onAcceptingConnections: (InternetAddress address, int port) {
final String host = address.isLoopback ? 'localhost' : address.host;
print('Serving requests at http://$host:$port/');
});
});
}