blob: 95af002ff11477a0f9721eddfbc9e2ad8716c176 [file] [log] [blame]
#!/usr/bin/env dart
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Find the success/failure status for a builder that is written to
// Firestore by the cloud functions that process results.json.
// These cloud functions write a success/failure result to the
// builder table based on the approvals in Firestore.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
const numAttempts = 20;
bool booleanFieldOrFalse(Map<String, dynamic> document, String field) {
Map<String, dynamic> fieldObject = document['fields'][field];
if (fieldObject == null) return false;
return fieldObject['booleanValue'] ?? false;
}
void usage(ArgParser parser) {
print('''
Usage: get_builder_status.dart [OPTIONS]
Gets the builder status from the Firestore database.
Polls until it gets a completed builder status, or times out.
The options are as follows:
${parser.usage}''');
exit(1);
}
main(List<String> args) async {
final parser = new ArgParser();
parser.addFlag('help', help: 'Show the program usage.', negatable: false);
parser.addOption('auth_token',
abbr: 'a', help: 'Authorization token with cloud-platform scope');
parser.addOption('builder', abbr: 'b', help: 'The builder name');
parser.addOption('build_number', abbr: 'n', help: 'The build number');
final options = parser.parse(args);
if (options['help']) {
usage(parser);
}
const postUrl = 'https://firestore.googleapis.com/v1/'
'projects/dart-ci/databases/(default)/documents:runQuery';
final builder = options['builder'];
final buildNumber = options['build_number'];
final table = builder.endsWith('-try') ? 'try_builds' : 'builds';
final token = await File(options['auth_token']).readAsString();
final client = http.Client();
final query = {
"from": [
{"collectionId": table}
],
"limit": 1,
"where": {
"compositeFilter": {
"op": "AND",
"filters": [
{
"fieldFilter": {
"field": {"fieldPath": "build_number"},
"op": "EQUAL",
"value": {"integerValue": buildNumber}
}
},
{
"fieldFilter": {
"field": {"fieldPath": "builder"},
"op": "EQUAL",
"value": {"stringValue": builder}
}
}
]
}
}
};
final json = jsonEncode({"structuredQuery": query});
final headers = {
'Authorization': 'Bearer $token',
'Accept': 'application/json',
'Content-Type': 'application/json'
};
for (int count = 0; count < numAttempts; ++count) {
if (count > 0) {
await Future.delayed(Duration(seconds: 10));
}
final response = await client.post(postUrl, headers: headers, body: json);
if (response.statusCode == HttpStatus.ok) {
final documents = jsonDecode(response.body);
final document = documents.first['document'];
if (document != null) {
bool success = booleanFieldOrFalse(document, 'success');
bool completed = booleanFieldOrFalse(document, 'completed');
bool activeFailures = booleanFieldOrFalse(document, 'active_failures');
if (completed) {
if (success) {
print('No unapproved new failures');
if (activeFailures == true) {
print('There are unapproved failures from previous builds');
}
} else {
print('There are unapproved new failures on this build');
}
exit((success && (activeFailures != true)) ? 0 : 1);
}
String chunks =
(document['fields']['num_chunks'] ?? const {})['integerValue'];
String processedChunks = (document['fields']['processed_chunks'] ??
const {})['integerValue'];
if (processedChunks != null) {
print([
'Received',
processedChunks,
if (chunks != null) ...['out of', chunks],
'chunks.'
].join(' '));
}
} else {
print("No results recieved for build $buildNumber of $builder");
}
} else {
print("HTTP status ${response.statusCode} received "
"when fetching build data");
}
}
print('No status received for build $buildNumber of $builder '
'after $numAttempts attempts, with 10 second waits.');
exit(2);
}