blob: 3ce5dd3a4892eba9e9f135a7a4d4304f7953ddc5 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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:async';
import 'package:buildbucket/buildbucket.dart';
import 'package:http/http.dart' as http;
import '../enums.dart';
import '../service/build_info.dart';
import '../service/build_service.dart';
/// Provides a [BuildbucketApi].
typedef _ApiProvider = BuildbucketApi Function(http.Client client);
/// A [BuildService] implementation that fetches info from the build_bucket api.
class BuildBucketService implements BuildService {
static const Duration _timeoutDuration = const Duration(seconds: 10);
static const List<String> _allBuckets = const <String>[
'luci.fuchsia.ci',
];
int _requestCount = 0;
int _timeoutCount = 0;
_ApiProvider _createApi;
/// Initializing constructor.
BuildBucketService({_ApiProvider apiProvider})
: _createApi = apiProvider ?? _defaultApiFactory;
@override
double get timeoutRate =>
100 * (_requestCount > 0 ? _timeoutCount / _requestCount : 0.0);
@override
Stream<BuildInfo> getBuildByName(String buildName) async* {
http.Client httpClient;
ApiSearchResponseMessage response;
try {
// Create a new HTTP client per request to prevent opening infinite sockets.
httpClient = new http.Client();
final Future<ApiSearchResponseMessage> request =
_createApi(httpClient).search(
bucket: _allBuckets,
tag: <String>['builder:$buildName'],
status: BuildStatusEnum.completed.value,
);
_requestCount++;
response = await request.timeout(_timeoutDuration, onTimeout: () {
_timeoutCount++;
throw new TimeoutException(
'$buildName exceeded ${_timeoutDuration.inSeconds} seconds');
});
} finally {
httpClient?.close();
}
if (response?.error != null) {
throw new BuildServiceException(response.error.toJson().toString());
}
yield _createBuildInfo(response.builds.first);
}
/// Fetch builds with names from [buildNames].
///
/// On build bucket, this given build name corresponds to the name of the
/// builder which is derived from the build response's tags.
@override
Stream<List<BuildInfo>> getBuildsByName(List<String> buildNames) async* {
yield await Future
.wait(buildNames.map((String name) => getBuildByName(name).first));
}
/// Creates a new [BuildbucketApi] given [httpClient].
static BuildbucketApi _defaultApiFactory(http.Client httpClient) =>
new BuildbucketApi(httpClient);
/// Returns the "name" of the build if it is present. Otherwise, null.
String _getBuildName(ApiCommonBuildMessage build) {
return build.tags?.isNotEmpty == true
? build.tags.first.split(':')[1]
: null;
}
String _getBuildType(ApiCommonBuildMessage build) {
return _getBuildName(build).split('-').first;
}
BuildInfo _createBuildInfo(ApiCommonBuildMessage build) => new BuildInfo(
bucket: build.bucket,
name: _getBuildName(build),
result: BuildResultEnum.from(build.result),
status: BuildStatusEnum.from(build.status),
type: _getBuildType(build),
url: build.url,
);
}