| Index: tools/gardening/lib/src/luci_api.dart
|
| diff --git a/tools/gardening/lib/src/luci_api.dart b/tools/gardening/lib/src/luci_api.dart
|
| deleted file mode 100644
|
| index 9a881cdfd7094869561e53cc2fcdc4cf2432acd6..0000000000000000000000000000000000000000
|
| --- a/tools/gardening/lib/src/luci_api.dart
|
| +++ /dev/null
|
| @@ -1,385 +0,0 @@
|
| -// Copyright (c) 2017, 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.
|
| -
|
| -import 'dart:io';
|
| -import 'dart:async';
|
| -import 'dart:convert';
|
| -import 'package:http/http.dart' as http;
|
| -import 'package:html/parser.dart' show parse;
|
| -import 'try.dart';
|
| -import 'cache_new.dart';
|
| -
|
| -const String LUCI_HOST = "luci-milo.appspot.com";
|
| -
|
| -typedef void ModifyRequestFunction(HttpClientRequest request);
|
| -
|
| -/// Base class for communicating with [Luci]
|
| -/// Some information is found through the api
|
| -/// <https://docs.google.com/document/d/1HbPp7Sy7ofC
|
| -/// U7C9USqcE91VubGg_IIET2GUj9iknev4/edit#>
|
| -/// and some information is found via screen-scraping.
|
| -class LuciApi {
|
| - final HttpClient _client = new HttpClient();
|
| -
|
| - LuciApi();
|
| -
|
| - /// [getBuildBots] fetches all build bots from luci (we cannot
|
| - /// get this from the api). The format is:
|
| - /// <li>
|
| - /// <a href="/buildbot/client.crashpad/crashpad_win_x86_wow64_rel">
|
| - /// crashpad_win_x86_wow64_rel</a>
|
| - /// </li>
|
| - /// <h3> client.dart </h3>
|
| - /// <li>
|
| - /// <a href="/buildbot/client.dart/analyze-linux-be">analyze-linux-be</a>
|
| - /// </li>
|
| - /// <li>
|
| - /// <a href="/buildbot/client.dart/analyze-linux-stable">
|
| - /// analyze-linux-stable</a>
|
| - /// </li>
|
| - /// <li>
|
| - /// <a href="/buildbot/client.dart/analyzer-linux-release-be">
|
| - /// analyzer-linux-release-be</a>
|
| - /// </li>
|
| - ///
|
| - /// We look for the section header matching clients, then
|
| - /// if we are in the right section, we take the <li> element
|
| - /// and transform to a build bot
|
| - ///
|
| - Future<Try<List<LuciBuildBot>>> getAllBuildBots(
|
| - String client, WithCacheFunction withCache) async {
|
| - return await tryStartAsync(() => withCache(
|
| - () => _makeGetRequest(
|
| - new Uri(scheme: 'https', host: LUCI_HOST, path: "/")),
|
| - "all_buildbots"))
|
| - .then((Try<String> tryRes) => tryRes.bind(parse).bind((htmlDoc) {
|
| - // This is really dirty, but the structure of
|
| - // the document is not really suited for anything else.
|
| - var takeSection = false;
|
| - return htmlDoc.body.children.where((node) {
|
| - if (node.localName == "li") return takeSection;
|
| - if (node.localName != "h3") {
|
| - takeSection = false;
|
| - return false;
|
| - }
|
| - // Current node is <h3>.
|
| - takeSection = client == node.text.trim();
|
| - return false;
|
| - });
|
| - }).bind((elements) {
|
| - // Here we hold an iterable of buildbot elements
|
| - // <li>
|
| - // <a href="/buildbot/client.dart/analyzer-linux-release-be">
|
| - // analyzer-linux-release-be</a>
|
| - // </li>
|
| - return elements.map((element) {
|
| - var name = element.children[0].text;
|
| - var url = element.children[0].attributes['href'];
|
| - return new LuciBuildBot(client, name, url);
|
| - }).toList();
|
| - }));
|
| - }
|
| -
|
| - /// [getPrimaryBuilders] fetches all primary builders
|
| - /// (the ones usually used by gardeners) by not including buildbots with
|
| - /// the name -dev, -stable or -integration.
|
| - Future<Try<List<LuciBuildBot>>> getPrimaryBuilders(
|
| - String client, WithCacheFunction withCache) async {
|
| - return await getAllBuildBots(client, withCache)
|
| - .then((Try<List<LuciBuildBot>> tryRes) {
|
| - return tryRes
|
| - .bind((buildBots) => buildBots.where((LuciBuildBot buildBot) {
|
| - return !(buildBot.name.contains("-dev") ||
|
| - buildBot.name.contains("-stable") ||
|
| - buildBot.name.contains("-integration"));
|
| - }));
|
| - });
|
| - }
|
| -
|
| - /// Calling the Milo Api to get latest builds for this bot,
|
| - /// where the field [amount] is the number of recent builds to fetch.
|
| - Future<Try<List<BuildDetail>>> getBuildBotDetails(
|
| - String client, String botName, WithCacheFunction withCache,
|
| - [int amount = 20]) async {
|
| - var uri = new Uri(
|
| - scheme: "https",
|
| - host: LUCI_HOST,
|
| - path: "prpc/milo.Buildbot/GetBuildbotBuildsJSON");
|
| - var body = {
|
| - "master": client,
|
| - "builder": botName,
|
| - "limit": amount,
|
| - "includeCurrent": true
|
| - };
|
| - var result = await tryStartAsync(() => withCache(
|
| - () => _makePostRequest(uri, JSON.encode(body), {
|
| - HttpHeaders.CONTENT_TYPE: "application/json",
|
| - HttpHeaders.ACCEPT: "application/json"
|
| - }),
|
| - '${uri.path}_${botName}_$amount'));
|
| - return result.bind(JSON.decode).bind((json) {
|
| - return json["builds"].map((b) {
|
| - var build = JSON.decode(UTF8.decode(BASE64.decode(b["data"])));
|
| - return getBuildDetailFromJson(client, botName, build);
|
| - }).toList();
|
| - });
|
| - }
|
| -
|
| - /// Calling the Milo Api to get information about a specific build
|
| - /// where the field [buildNumber] is the build number to fetch.
|
| - Future<Try<BuildDetail>> getBuildBotBuildDetails(String client,
|
| - String botName, int buildNumber, WithCacheFunction withCache) async {
|
| - var uri = new Uri(
|
| - scheme: "https",
|
| - host: LUCI_HOST,
|
| - path: "prpc/milo.Buildbot/GetBuildbotBuildJSON");
|
| - var body = {"master": client, "builder": botName, "buildNum": buildNumber};
|
| - print(body);
|
| - var result = await tryStartAsync(() => withCache(
|
| - () => _makePostRequest(uri, JSON.encode(body), {
|
| - HttpHeaders.CONTENT_TYPE: "application/json",
|
| - HttpHeaders.ACCEPT: "application/json"
|
| - }),
|
| - '${uri.path}_${botName}_$buildNumber'));
|
| - return result.bind(JSON.decode).bind((json) {
|
| - var build = JSON.decode(UTF8.decode(BASE64.decode(json["data"])));
|
| - return getBuildDetailFromJson(client, botName, build);
|
| - });
|
| - }
|
| -
|
| - /// [_makeGetRequest] performs a get request to [uri].
|
| - Future<String> _makeGetRequest(Uri uri) async {
|
| - var request = await _client.getUrl(uri);
|
| - var response = await request.close();
|
| - if (response.statusCode != 200) {
|
| - response.drain();
|
| - throw new HttpException(response.reasonPhrase, uri: uri);
|
| - }
|
| - return response.transform(UTF8.decoder).join();
|
| - }
|
| -
|
| - /// [_makeGetRequest] performs a post request to [uri], where the posted
|
| - /// body is the string representation of [body]. For adding custom headers
|
| - /// use the map [headers].
|
| - Future<String> _makePostRequest(
|
| - Uri uri, Object body, Map<String, String> headers) async {
|
| - var response = await http.post(uri, body: body, headers: headers);
|
| - if (response.statusCode != 200) {
|
| - throw new HttpException(response.reasonPhrase, uri: uri);
|
| - }
|
| - // Prpc outputs a prefix to combat vulnerability.
|
| - if (response.body.startsWith(")]}'")) {
|
| - return response.body.substring(4);
|
| - }
|
| - return response.body;
|
| - }
|
| -
|
| - /// Closes the Http client connection
|
| - void close() {
|
| - _client.close();
|
| - }
|
| -}
|
| -
|
| -/// [getBuildDetailFromJson] parses json [build] to a class [BuildDetail]
|
| -BuildDetail getBuildDetailFromJson(
|
| - String client, String botName, dynamic build) {
|
| - List<GitCommit> changes = build["sourceStamp"]["changes"].map((change) {
|
| - return new GitCommit(change["revision"], change["revLink"], change["who"],
|
| - change["comments"], change["files"].map((file) => file["name"]));
|
| - }).toList();
|
| -
|
| - List<BuildProperty> properties = build["properties"].map((prop) {
|
| - return new BuildProperty(prop[0], prop[1].toString(), prop[2]);
|
| - }).toList();
|
| -
|
| - List<BuildStep> steps = build["steps"].map((step) {
|
| - var start =
|
| - new DateTime.fromMillisecondsSinceEpoch(step["times"][0] * 1000);
|
| - DateTime end = null;
|
| - if (step["times"][1] != null) {
|
| - end = new DateTime.fromMillisecondsSinceEpoch(step["times"][1] * 1000);
|
| - }
|
| - return new BuildStep(
|
| - step["name"],
|
| - step["text"],
|
| - step["results"].toString(),
|
| - start,
|
| - end,
|
| - step["step_number"],
|
| - step["isStarted"],
|
| - step["isFinished"],
|
| - step["logs"].map((log) => new BuildLog(log[0], log[1])));
|
| - }).toList();
|
| -
|
| - DateTime end = null;
|
| - if (build["times"][1] != null) {
|
| - end = new DateTime.fromMillisecondsSinceEpoch(build["times"][1] * 1000);
|
| - }
|
| -
|
| - Timing timing = new Timing(
|
| - new DateTime.fromMillisecondsSinceEpoch(build["times"][0] * 1000), end);
|
| -
|
| - return new BuildDetail(
|
| - client,
|
| - botName,
|
| - build["number"],
|
| - build["text"].join(' '),
|
| - build["finished"],
|
| - steps,
|
| - properties,
|
| - build["blame"],
|
| - timing,
|
| - changes);
|
| -}
|
| -
|
| -// Structured classes to relay information from api and web pages
|
| -
|
| -/// [LuciBuildBot] holds information about a build bot
|
| -class LuciBuildBot {
|
| - final String client;
|
| - final String name;
|
| - final String url;
|
| -
|
| - LuciBuildBot(this.client, this.name, this.url);
|
| -
|
| - @override
|
| - String toString() {
|
| - return "LuciBuildBot { client: $client, name: $name, url: $url }";
|
| - }
|
| -}
|
| -
|
| -/// [BuildDetail] holds data detailing a specific build
|
| -class BuildDetail {
|
| - final String client;
|
| - final String botName;
|
| - final int buildNumber;
|
| - final String results;
|
| - final bool finished;
|
| - final List<BuildStep> steps;
|
| - final List<BuildProperty> buildProperties;
|
| - final List<String> blameList;
|
| - final Timing timing;
|
| - final List<GitCommit> allChanges;
|
| -
|
| - BuildDetail(
|
| - this.client,
|
| - this.botName,
|
| - this.buildNumber,
|
| - this.results,
|
| - this.finished,
|
| - this.steps,
|
| - this.buildProperties,
|
| - this.blameList,
|
| - this.timing,
|
| - this.allChanges);
|
| -
|
| - @override
|
| - String toString() {
|
| - StringBuffer buffer = new StringBuffer();
|
| - buffer.writeln("--------------------------------------");
|
| - buffer.writeln(results);
|
| - buffer.writeln(timing);
|
| - buffer.writeln("----------------STEPS-----------------");
|
| - if (steps != null) steps.forEach(buffer.writeln);
|
| - buffer.writeln("----------BUILD PROPERTIES------------");
|
| - if (buildProperties != null) buildProperties.forEach(buffer.writeln);
|
| - buffer.writeln("-------------BLAME LIST---------------");
|
| - if (blameList != null) blameList.forEach(buffer.writeln);
|
| - buffer.writeln("------------ALL CHANGES---------------");
|
| - if (allChanges != null) allChanges.forEach(buffer.writeln);
|
| - return buffer.toString();
|
| - }
|
| -}
|
| -
|
| -/// [BuildStep] holds data detailing a specific build
|
| -class BuildStep {
|
| - final String name;
|
| - final String description;
|
| - final String result;
|
| - final DateTime start;
|
| - final DateTime end;
|
| - final int number;
|
| - final bool isStarted;
|
| - final bool isFinished;
|
| - final List<BuildLog> logs;
|
| -
|
| - BuildStep(this.name, this.description, this.result, this.start, this.end,
|
| - this.number, this.isStarted, this.isFinished, this.logs);
|
| -
|
| - @override
|
| - String toString() {
|
| - StringBuffer buffer = new StringBuffer();
|
| - buffer.writeln("${result == '[0, []]' ? 'SUCCESS' : result}: "
|
| - "$name - $description ($start, $end)");
|
| - logs.forEach((subLink) {
|
| - buffer.writeln("\t${subLink}");
|
| - });
|
| - return buffer.toString();
|
| - }
|
| -}
|
| -
|
| -/// [BuildLog] holds log-information for a specific build.
|
| -class BuildLog {
|
| - final String name;
|
| - final String url;
|
| -
|
| - BuildLog(this.name, this.url);
|
| -
|
| - @override
|
| - String toString() {
|
| - return "$name | $url";
|
| - }
|
| -}
|
| -
|
| -/// [BuildProperty] descibes build properties of a specific build.
|
| -class BuildProperty {
|
| - final String name;
|
| - final String value;
|
| - final String source;
|
| -
|
| - BuildProperty(this.name, this.value, this.source);
|
| -
|
| - @override
|
| - String toString() {
|
| - return "$name\t$value\t$source";
|
| - }
|
| -}
|
| -
|
| -/// [Timing] is a class to hold timing information for builds and steps.
|
| -class Timing {
|
| - final DateTime start;
|
| - final DateTime end;
|
| -
|
| - Timing(this.start, this.end);
|
| -
|
| - @override
|
| - String toString() {
|
| - return "start: $start\tend: $end";
|
| - }
|
| -}
|
| -
|
| -/// [GitCommit] holds data about a specific commit.
|
| -class GitCommit {
|
| - final String revision;
|
| - final String commitUrl;
|
| - final String changedBy;
|
| - final String comments;
|
| - final List<String> changedFiles;
|
| -
|
| - GitCommit(this.revision, this.commitUrl, this.changedBy, this.comments,
|
| - this.changedFiles);
|
| -
|
| - @override
|
| - String toString() {
|
| - StringBuffer buffer = new StringBuffer();
|
| - buffer.writeln("revision: $revision");
|
| - buffer.writeln("commitUrl: $commitUrl");
|
| - buffer.writeln("changedBy: $changedBy");
|
| - buffer.write("\n");
|
| - buffer.writeln(comments);
|
| - buffer.write("\nfiles:\n");
|
| - changedFiles.forEach(buffer.writeln);
|
| - return buffer.toString();
|
| - }
|
| -}
|
|
|