diff --git a/app_dart/bin/gae_server.dart b/app_dart/bin/gae_server.dart index a21fd77ef8..ddd1f61d9a 100644 --- a/app_dart/bin/gae_server.dart +++ b/app_dart/bin/gae_server.dart @@ -15,6 +15,7 @@ import 'package:cocoon_service/src/service/build_status_service.dart'; import 'package:cocoon_service/src/service/commit_service.dart'; import 'package:cocoon_service/src/service/content_aware_hash_service.dart'; import 'package:cocoon_service/src/service/firebase_jwt_validator.dart'; +import 'package:cocoon_service/src/service/flags/dynamic_config_updater.dart'; import 'package:cocoon_service/src/service/get_files_changed.dart'; import 'package:cocoon_service/src/service/scheduler/ci_yaml_fetcher.dart'; import 'package:logging/logging.dart'; @@ -47,7 +48,7 @@ Future main() async { const GoogleAuthProvider(), projectId: Config.flutterGcpProjectId, ), - dynamicConfig: dynamicConfig, + initialConfig: dynamicConfig, ); // Start updating the config to loop forever. If this fails, it will log // every ~1 minute. diff --git a/app_dart/lib/src/service/config.dart b/app_dart/lib/src/service/config.dart index a31f75471f..0a7c05723f 100644 --- a/app_dart/lib/src/service/config.dart +++ b/app_dart/lib/src/service/config.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:convert'; -import 'dart:math' show Random; import 'dart:typed_data'; import 'package:cocoon_server/generate_github_jws.dart'; @@ -12,30 +11,20 @@ import 'package:cocoon_server/secret_manager.dart'; import 'package:github/github.dart' as gh; import 'package:graphql/client.dart' hide JsonSerializable; import 'package:http/http.dart' as http; -import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; import 'package:retry/retry.dart'; -import 'package:yaml/yaml.dart' show YamlList, YamlMap, loadYaml; import '../../cocoon_service.dart'; -import '../foundation/providers.dart' show Providers; -import '../foundation/typedefs.dart' show HttpClientProvider; -import 'flags/content_aware_hashing_flags.dart'; +import 'flags/dynamic_config_updater.dart'; import 'github_service.dart'; import 'luci_build_service/cipd_version.dart'; -part 'config.g.dart'; - /// Name of the default git branch. const String kDefaultBranchName = 'master'; -interface class Config { +interface class Config extends DynamicallyUpdatedConfig { /// Creates and returns a [Config] instance. - Config(this._cache, this._secrets, {required DynamicConfig dynamicConfig}) - : _dynamicConfig = dynamicConfig; - - /// Access dynamically configured flags. - DynamicConfig get flags => _dynamicConfig; + Config(this._cache, this._secrets, {required super.initialConfig}); /// When present on a pull request, instructs Cocoon to submit it /// automatically as soon as all the required checks pass. @@ -71,8 +60,6 @@ interface class Config { final CacheService _cache; final SecretManager _secrets; - DynamicConfig _dynamicConfig; - /// List of Github presubmit supported repos. /// /// This adds support for the `waiting for tree to go green label` to the repo. @@ -497,167 +484,3 @@ interface class Config { return GithubService(github); } } - -/// Flags for the service that can be updated dynamically with out a restart. -/// -/// Should be read from git/HEAD/app_dart/config.yaml and cached between -/// services. -@JsonSerializable(explicitToJson: true) -@immutable -final class DynamicConfig { - /// Upper limit of commit rows to be backfilled in API call. - /// - /// This limits the number of commits to be checked to backfill. When bots - /// are idle, we hope to scan as many commit rows as possible. - @JsonKey(defaultValue: 50) - final int backfillerCommitLimit; - - final ContentAwareHashingJson contentAwareHashing; - - DynamicConfig({ - required this.backfillerCommitLimit, - required this.contentAwareHashing, - }); - - /// Connect the generated [_$DynamicConfigFromJson] function to the `fromJson` - /// factory. - factory DynamicConfig.fromJson(Map? json) => - _$DynamicConfigFromJson(json ?? {}); - - /// Connect the generated [_$DynamicConfigToJson] function to the `toJson` method. - Map toJson() => _$DynamicConfigToJson(this); -} - -extension YamlMapToMap on YamlMap { - Map get asMap => { - for (final MapEntry(:key, :value) in entries) - if (value is YamlMap) - '$key': value.asMap - else if (value is YamlList) - '$key': value.asList - else - '$key': value, - }; -} - -extension YamlListToList on YamlList { - List get asList => [ - for (final value in nodes) - if (value is YamlMap) - value.asMap - else if (value is YamlList) - value.asList - else - value, - ]; -} - -/// Responsibly polls for configuration changes to our service config. -/// -/// This works by fetching the latest checked in "config.yaml". -class DynamicConfigUpdater { - DynamicConfigUpdater({ - Duration delay = const Duration(minutes: 1), - @visibleForTesting Random? random, - @visibleForTesting - HttpClientProvider httpClientProvider = Providers.freshHttpClient, - @visibleForTesting - RetryOptions retryOptions = const RetryOptions( - maxAttempts: 3, - delayFactor: Duration(seconds: 3), - ), - }) : _delay = delay, - _random = random ?? Random(), - _httpClientProvider = httpClientProvider, - _retryOptions = retryOptions; - - final Duration _delay; - final Random _random; - final HttpClientProvider _httpClientProvider; - final RetryOptions _retryOptions; - - /// Fetches and parses the `config.yaml` from HEAD `flutter/cocoon/app_dart/`. - Future fetchDynamicConfig() async { - final file = await githubFileContent( - Config.cocoonSlug, - 'app_dart/config.yaml', - ref: 'main', - httpClientProvider: _httpClientProvider, - retryOptions: _retryOptions, - ); - final configYaml = loadYaml(file) as YamlMap; - return DynamicConfig.fromJson(configYaml.asMap); - } - - UpdaterStatus _status = UpdaterStatus.stopped; - - void stopUpdateLoop() { - if (_status != UpdaterStatus.running) return; - log.info('ConfigUpdater: Stopping config update loop...'); - _status = UpdaterStatus.stopping; - } - - void startUpdateLoop(Config config) async { - if (_status != UpdaterStatus.stopped) return; - _status = UpdaterStatus.running; - - log.info('ConfigUpdater: Starting config update loop...'); - - // What we've decided: - // 1. Each instance will **start** with a valid DynamicConfig - // 2. Each instance will update their own config on an interval that can - // drift by as much as a minute. - // 3. If a fetch fails, we'll log an error, but keep using the last config - while (true) { - await Future.delayed( - _delay + Duration(milliseconds: _random.nextInt(1000)), - ); - if (_status != UpdaterStatus.running) { - log.info('ConfigUpdater: Stopped config update loop'); - _status = UpdaterStatus.stopped; - return; - } - try { - final dynamicConfig = await fetchDynamicConfig(); - final diffs = diffConfigChanges( - config._dynamicConfig.toJson(), - dynamicConfig.toJson(), - ); - if (diffs.isNotEmpty) { - log.info('ConfigUpdater: ${diffs.join(',')}'); - config._dynamicConfig = dynamicConfig; - } - } catch (e, s) { - log.error('ConfigUpdater: Unable to fetch DynamicConfig!', e, s); - } - } - } - - /// Produce a simple diff of the changing flags. - List diffConfigChanges( - Map oldFlags, - Map newFlags, { - List? diffs, - String chain = 'flags', - }) { - diffs ??= []; - - for (final MapEntry(:key, :value) in oldFlags.entries) { - if (value is Map) { - diffConfigChanges( - value as Map, - newFlags[key] as Map, - diffs: diffs, - chain: '$chain.$key', - ); - continue; - } - if (value != newFlags[key]) { - diffs.add('$chain.$key $value -> ${newFlags[key]}'); - } - } - return diffs; - } -} - -enum UpdaterStatus { stopped, running, stopping } diff --git a/app_dart/lib/src/service/flags/content_aware_hashing_flags.dart b/app_dart/lib/src/service/flags/content_aware_hashing_flags.dart index 584e61a90f..623fc70c6c 100644 --- a/app_dart/lib/src/service/flags/content_aware_hashing_flags.dart +++ b/app_dart/lib/src/service/flags/content_aware_hashing_flags.dart @@ -7,20 +7,39 @@ import 'package:meta/meta.dart'; part 'content_aware_hashing_flags.g.dart'; +/// Flags related to content-aware hashing. @JsonSerializable() @immutable -final class ContentAwareHashingJson { - ContentAwareHashingJson({required this.waitOnContentHash}); +final class ContentAwareHashing { + /// Default configuration for [ContentAwareHashing] flags. + static const defaultInstance = ContentAwareHashing._( + waitOnContentHash: false, + ); - /// Merge Groups should wait for the content hash before scheduling. - @JsonKey(defaultValue: false) + /// Whether merge groups should wait for the content hash before scheduling. + @JsonKey() final bool waitOnContentHash; - /// Connect the generated [_$ContentAwareHashingJsonFromJson] function to the `fromJson` - /// factory. - factory ContentAwareHashingJson.fromJson(Map? json) => - _$ContentAwareHashingJsonFromJson(json ?? {}); + const ContentAwareHashing._({ + required this.waitOnContentHash, // + }); - /// Connect the generated [_$ContentAwareHashingJsonToJson] function to the `toJson` method. - Map toJson() => _$ContentAwareHashingJsonToJson(this); + /// Creates [ContentAwareHashing] flags from the provided fields. + /// + /// Any omitted fields default to the values in [defaultInstance]. + factory ContentAwareHashing({bool? waitOnContentHash}) { + return ContentAwareHashing._( + waitOnContentHash: waitOnContentHash ?? defaultInstance.waitOnContentHash, + ); + } + + /// Creates [ContentAwareHashing] flags from a [json] object. + /// + /// Any omitted fields default to the values in [defaultInstance]. + factory ContentAwareHashing.fromJson(Map? json) { + return _$ContentAwareHashingFromJson(json ?? {}); + } + + /// The inverse operation of [ContentAwareHashing.fromJson]. + Map toJson() => _$ContentAwareHashingToJson(this); } diff --git a/app_dart/lib/src/service/flags/content_aware_hashing_flags.g.dart b/app_dart/lib/src/service/flags/content_aware_hashing_flags.g.dart index dd6008b444..77fb7df02c 100644 --- a/app_dart/lib/src/service/flags/content_aware_hashing_flags.g.dart +++ b/app_dart/lib/src/service/flags/content_aware_hashing_flags.g.dart @@ -8,12 +8,9 @@ part of 'content_aware_hashing_flags.dart'; // JsonSerializableGenerator // ************************************************************************** -ContentAwareHashingJson _$ContentAwareHashingJsonFromJson( - Map json, -) => ContentAwareHashingJson( - waitOnContentHash: json['waitOnContentHash'] as bool? ?? false, -); +ContentAwareHashing _$ContentAwareHashingFromJson(Map json) => + ContentAwareHashing(waitOnContentHash: json['waitOnContentHash'] as bool?); -Map _$ContentAwareHashingJsonToJson( - ContentAwareHashingJson instance, +Map _$ContentAwareHashingToJson( + ContentAwareHashing instance, ) => {'waitOnContentHash': instance.waitOnContentHash}; diff --git a/app_dart/lib/src/service/flags/dynamic_config.dart b/app_dart/lib/src/service/flags/dynamic_config.dart new file mode 100644 index 0000000000..7a5b2d3865 --- /dev/null +++ b/app_dart/lib/src/service/flags/dynamic_config.dart @@ -0,0 +1,139 @@ +// 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. + +/// @docImport '../config.dart'; +/// @docImport 'dynamic_config_updater.dart'; +library; + +import 'dart:io' as io; + +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as p; +import 'package:yaml/yaml.dart'; + +import 'content_aware_hashing_flags.dart'; +import 'dynamic_config_updater.dart'; + +part 'dynamic_config.g.dart'; + +/// Flags for the service that can be updated dynamically with out a restart. +/// +/// Normally, this object is from [Config.flags], and is updated in the +/// background automatically by [DynamicConfigUpdater], where the latest copy +/// of `//app_dart/config.yaml` is fetched periodically. +/// +/// To get a one-time copy of the file, see also: +/// +/// - [DynamicConfig.fromRemoteLatest] +/// - [DynamicConfig.fromLocalFileSystem] +@JsonSerializable(explicitToJson: true) +@immutable +final class DynamicConfig { + /// Default configuration for flags. + static const defaultInstance = DynamicConfig._( + backfillerCommitLimit: 50, + contentAwareHashing: ContentAwareHashing.defaultInstance, + ); + + /// Upper limit of commit rows to be backfilled in API call. + /// + /// This limits the number of commits to be checked to backfill. When bots + /// are idle, we hope to scan as many commit rows as possible. + @JsonKey() + final int backfillerCommitLimit; + + /// Flags associated with content-aware hashing. + @JsonKey() + final ContentAwareHashing contentAwareHashing; + + const DynamicConfig._({ + required this.backfillerCommitLimit, + required this.contentAwareHashing, + }); + + /// Creates [DynamicConfig] flags from a [json] object. + /// + /// Any omitted fields default to the values in [defaultInstance]. + factory DynamicConfig({ + int? backfillerCommitLimit, + ContentAwareHashing? contentAwareHashing, + }) { + return DynamicConfig._( + backfillerCommitLimit: + backfillerCommitLimit ?? defaultInstance.backfillerCommitLimit, + contentAwareHashing: + contentAwareHashing ?? defaultInstance.contentAwareHashing, + ); + } + + /// Creates [DynamicConfig] flags from a [json] object. + /// + /// Any omitted fields default to the values in [defaultInstance]. + factory DynamicConfig.fromJson(Map? json) { + return _$DynamicConfigFromJson(json ?? {}); + } + + /// Creates [DynamicConfig] flags from a [yaml] object. + factory DynamicConfig.fromYaml(YamlMap? yaml) { + return DynamicConfig.fromJson(yaml?.asMap); + } + + /// Returns the latest copy of [DynamicConfig] fetched from tip-of-tree. + /// + /// Equivalent to a single call to [DynamicConfigUpdater.fetchDynamicConfig]. + static Future fromRemoteLatest() { + return DynamicConfigUpdater().fetchDynamicConfig(); + } + + /// Returns the latest copy of [DynamicConfig] fetched from the repository. + static Future fromLocalFileSystem() async { + final execPath = io.Platform.resolvedExecutable; + + // Walk backwards until the root of the Cocoon repository is found. + var dir = io.File(execPath).parent; + while (dir.path != dir.parent.path) { + final gitDir = io.Directory(p.join(dir.path, '.git')); + if (await gitDir.exists()) { + break; + } + dir = dir.parent; + } + + final configPath = io.File(p.join(dir.path, 'app_dart', 'config.yaml')); + if (!await configPath.exists()) { + throw StateError('Could not find config.yaml at ${configPath.path}'); + } + + final yaml = loadYaml(await configPath.readAsString()) as YamlMap; + return DynamicConfig.fromYaml(yaml); + } + + /// The inverse operation of [DynamicConfig.fromJson]. + Map toJson() => _$DynamicConfigToJson(this); +} + +extension _YamlMapToMap on YamlMap { + Map get asMap => { + for (final MapEntry(:key, :value) in entries) + if (value is YamlMap) + '$key': value.asMap + else if (value is YamlList) + '$key': value.asList + else + '$key': value, + }; +} + +extension _YamlListToList on YamlList { + List get asList => [ + for (final value in nodes) + if (value is YamlMap) + value.asMap + else if (value is YamlList) + value.asList + else + value, + ]; +} diff --git a/app_dart/lib/src/service/config.g.dart b/app_dart/lib/src/service/flags/dynamic_config.g.dart similarity index 65% rename from app_dart/lib/src/service/config.g.dart rename to app_dart/lib/src/service/flags/dynamic_config.g.dart index 9ef740ed48..b5aed18373 100644 --- a/app_dart/lib/src/service/config.g.dart +++ b/app_dart/lib/src/service/flags/dynamic_config.g.dart @@ -2,7 +2,7 @@ // ignore_for_file: always_specify_types, implicit_dynamic_parameter -part of 'config.dart'; +part of 'dynamic_config.dart'; // ************************************************************************** // JsonSerializableGenerator @@ -10,11 +10,13 @@ part of 'config.dart'; DynamicConfig _$DynamicConfigFromJson(Map json) => DynamicConfig( - backfillerCommitLimit: - (json['backfillerCommitLimit'] as num?)?.toInt() ?? 50, - contentAwareHashing: ContentAwareHashingJson.fromJson( - json['contentAwareHashing'] as Map?, - ), + backfillerCommitLimit: (json['backfillerCommitLimit'] as num?)?.toInt(), + contentAwareHashing: + json['contentAwareHashing'] == null + ? null + : ContentAwareHashing.fromJson( + json['contentAwareHashing'] as Map?, + ), ); Map _$DynamicConfigToJson(DynamicConfig instance) => diff --git a/app_dart/lib/src/service/flags/dynamic_config_updater.dart b/app_dart/lib/src/service/flags/dynamic_config_updater.dart new file mode 100644 index 0000000000..cf2e967f59 --- /dev/null +++ b/app_dart/lib/src/service/flags/dynamic_config_updater.dart @@ -0,0 +1,135 @@ +// 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:math' show Random; +import 'package:cocoon_server/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:retry/retry.dart'; +import 'package:yaml/yaml.dart' show YamlMap, loadYaml; + +import '../../../cocoon_service.dart'; +import '../../foundation/providers.dart' show Providers; +import '../../foundation/typedefs.dart' show HttpClientProvider; +import 'dynamic_config.dart'; + +/// Responsibly polls for configuration changes to our service config. +/// +/// This works by fetching the latest checked in "config.yaml". +interface class DynamicConfigUpdater { + DynamicConfigUpdater({ + Duration delay = const Duration(minutes: 1), + @visibleForTesting Random? random, + @visibleForTesting + HttpClientProvider httpClientProvider = Providers.freshHttpClient, + @visibleForTesting + RetryOptions retryOptions = const RetryOptions( + maxAttempts: 3, + delayFactor: Duration(seconds: 3), + ), + }) : _delay = delay, + _random = random ?? Random(), + _httpClientProvider = httpClientProvider, + _retryOptions = retryOptions; + + final Duration _delay; + final Random _random; + final HttpClientProvider _httpClientProvider; + final RetryOptions _retryOptions; + + /// Fetches and parses the `config.yaml` from HEAD `flutter/cocoon/app_dart/`. + Future fetchDynamicConfig() async { + final file = await githubFileContent( + Config.cocoonSlug, + 'app_dart/config.yaml', + ref: 'main', + httpClientProvider: _httpClientProvider, + retryOptions: _retryOptions, + ); + final configYaml = loadYaml(file) as YamlMap; + return DynamicConfig.fromYaml(configYaml); + } + + _UpdaterStatus _status = _UpdaterStatus.stopped; + + void stopUpdateLoop() { + if (_status != _UpdaterStatus.running) return; + log.info('ConfigUpdater: Stopping config update loop...'); + _status = _UpdaterStatus.stopping; + } + + void startUpdateLoop(DynamicallyUpdatedConfig config) async { + if (_status != _UpdaterStatus.stopped) return; + _status = _UpdaterStatus.running; + + log.info('ConfigUpdater: Starting config update loop...'); + + // What we've decided: + // 1. Each instance will **start** with a valid DynamicConfig + // 2. Each instance will update their own config on an interval that can + // drift by as much as a minute. + // 3. If a fetch fails, we'll log an error, but keep using the last config + while (true) { + await Future.delayed( + _delay + Duration(milliseconds: _random.nextInt(1000)), + ); + if (_status != _UpdaterStatus.running) { + log.info('ConfigUpdater: Stopped config update loop'); + _status = _UpdaterStatus.stopped; + return; + } + try { + final dynamicConfig = await fetchDynamicConfig(); + final diffs = _diffConfigChanges( + config._dynamicConfig.toJson(), + dynamicConfig.toJson(), + ); + if (diffs.isNotEmpty) { + log.info('ConfigUpdater: ${diffs.join(',')}'); + config._dynamicConfig = dynamicConfig; + } + } catch (e, s) { + log.error('ConfigUpdater: Unable to fetch DynamicConfig!', e, s); + } + } + } + + /// Produce a simple diff of the changing flags. + List _diffConfigChanges( + Map oldFlags, + Map newFlags, { + List? diffs, + String chain = 'flags', + }) { + diffs ??= []; + + for (final MapEntry(:key, :value) in oldFlags.entries) { + if (value is Map) { + _diffConfigChanges( + value as Map, + newFlags[key] as Map, + diffs: diffs, + chain: '$chain.$key', + ); + continue; + } + if (value != newFlags[key]) { + diffs.add('$chain.$key $value -> ${newFlags[key]}'); + } + } + return diffs; + } +} + +/// A base type for a class where [flags] is updated at runtime. +abstract class DynamicallyUpdatedConfig { + DynamicallyUpdatedConfig({ + required DynamicConfig initialConfig, // + }) : _dynamicConfig = initialConfig; + + /// Access dynamically configured flags. + DynamicConfig get flags => _dynamicConfig; + DynamicConfig _dynamicConfig; +} + +enum _UpdaterStatus { stopped, running, stopping } diff --git a/app_dart/test/request_handlers/scheduler/batch_backfiller_test.dart b/app_dart/test/request_handlers/scheduler/batch_backfiller_test.dart index 59c0e30daf..368c0b82cb 100644 --- a/app_dart/test/request_handlers/scheduler/batch_backfiller_test.dart +++ b/app_dart/test/request_handlers/scheduler/batch_backfiller_test.dart @@ -9,6 +9,7 @@ import 'package:cocoon_service/src/request_handlers/scheduler/backfill_grid.dart import 'package:cocoon_service/src/request_handlers/scheduler/backfill_strategy.dart'; import 'package:cocoon_service/src/request_handlers/scheduler/batch_backfiller.dart'; import 'package:cocoon_service/src/service/config.dart'; +import 'package:cocoon_service/src/service/flags/dynamic_config.dart'; import 'package:cocoon_service/src/service/luci_build_service.dart'; import 'package:cocoon_service/src/service/luci_build_service/pending_task.dart'; import 'package:collection/collection.dart'; diff --git a/app_dart/test/service/config_test.dart b/app_dart/test/service/config_test.dart index 435590bc6f..c0e581c4cf 100644 --- a/app_dart/test/service/config_test.dart +++ b/app_dart/test/service/config_test.dart @@ -8,6 +8,7 @@ import 'dart:typed_data'; import 'package:cocoon_server_test/fake_secret_manager.dart'; import 'package:cocoon_server_test/test_logging.dart'; import 'package:cocoon_service/cocoon_service.dart'; +import 'package:cocoon_service/src/service/flags/dynamic_config.dart'; import 'package:github/github.dart'; import 'package:test/test.dart'; import 'package:yaml/yaml.dart'; @@ -25,7 +26,7 @@ void main() { config = Config( cacheService, secrets, - dynamicConfig: DynamicConfig.fromJson({}), + initialConfig: DynamicConfig.fromJson({}), ); }); @@ -70,7 +71,7 @@ void main() { test('current config.yaml is parsable', () async { final yaml = loadYaml(await File('config.yaml').readAsString()) as YamlMap; - DynamicConfig.fromJson(yaml.asMap); + DynamicConfig.fromYaml(yaml); }); }); } diff --git a/app_dart/test/service/dynamic_config_updater_test.dart b/app_dart/test/service/dynamic_config_updater_test.dart index 0967fbad67..b801a7f101 100644 --- a/app_dart/test/service/dynamic_config_updater_test.dart +++ b/app_dart/test/service/dynamic_config_updater_test.dart @@ -9,6 +9,8 @@ import 'package:cocoon_server/logging.dart' show log; import 'package:cocoon_server_test/fake_secret_manager.dart'; import 'package:cocoon_server_test/test_logging.dart'; import 'package:cocoon_service/cocoon_service.dart'; +import 'package:cocoon_service/src/service/flags/dynamic_config.dart'; +import 'package:cocoon_service/src/service/flags/dynamic_config_updater.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart' show MockClient; import 'package:mockito/mockito.dart'; @@ -41,7 +43,7 @@ void main() { config = Config( cacheService, secrets, - dynamicConfig: DynamicConfig.fromJson({}), + initialConfig: DynamicConfig.fromJson({}), ); updater = DynamicConfigUpdater( random: random, diff --git a/app_dart/test/service/scheduler/hash_workflow_test.dart b/app_dart/test/service/scheduler/hash_workflow_test.dart index b37872ddf0..cee56b21a6 100644 --- a/app_dart/test/service/scheduler/hash_workflow_test.dart +++ b/app_dart/test/service/scheduler/hash_workflow_test.dart @@ -11,6 +11,7 @@ import 'package:cocoon_service/src/model/firestore/content_aware_hash_builds.dar import 'package:cocoon_service/src/model/github/workflow_job.dart'; import 'package:cocoon_service/src/service/big_query.dart'; import 'package:cocoon_service/src/service/content_aware_hash_service.dart'; +import 'package:cocoon_service/src/service/flags/dynamic_config.dart'; import 'package:github/github.dart'; import 'package:http/http.dart'; import 'package:mockito/mockito.dart'; diff --git a/app_dart/test/service/scheduler_test.dart b/app_dart/test/service/scheduler_test.dart index 25e23450a8..07ba8190ab 100644 --- a/app_dart/test/service/scheduler_test.dart +++ b/app_dart/test/service/scheduler_test.dart @@ -18,6 +18,7 @@ import 'package:cocoon_service/src/model/firestore/pr_check_runs.dart'; import 'package:cocoon_service/src/model/firestore/task.dart' as fs; import 'package:cocoon_service/src/model/github/checks.dart' as cocoon_checks; import 'package:cocoon_service/src/service/big_query.dart'; +import 'package:cocoon_service/src/service/flags/dynamic_config.dart'; import 'package:cocoon_service/src/service/luci_build_service/engine_artifacts.dart'; import 'package:cocoon_service/src/service/luci_build_service/pending_task.dart'; import 'package:cocoon_service/src/service/scheduler/process_check_run_result.dart'; diff --git a/app_dart/test/src/fake_config.dart b/app_dart/test/src/fake_config.dart index 9c25b284e3..cb6fa73efe 100644 --- a/app_dart/test/src/fake_config.dart +++ b/app_dart/test/src/fake_config.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:cocoon_service/cocoon_service.dart'; +import 'package:cocoon_service/src/service/flags/dynamic_config.dart'; import 'package:cocoon_service/src/service/github_service.dart'; import 'package:cocoon_service/src/service/luci_build_service/cipd_version.dart'; import 'package:github/github.dart' as gh; diff --git a/app_dart/test/src/utilities/mocks.mocks.dart b/app_dart/test/src/utilities/mocks.mocks.dart index 688f25a774..569a923566 100644 --- a/app_dart/test/src/utilities/mocks.mocks.dart +++ b/app_dart/test/src/utilities/mocks.mocks.dart @@ -6,32 +6,33 @@ import 'dart:async' as _i13; import 'dart:convert' as _i12; import 'dart:io' as _i11; -import 'dart:typed_data' as _i26; +import 'dart:typed_data' as _i27; import 'package:buildbucket/buildbucket_pb.dart' as _i6; import 'package:cocoon_common/rpc_model.dart' as _i19; import 'package:cocoon_service/cocoon_service.dart' as _i17; import 'package:cocoon_service/src/foundation/github_checks_util.dart' as _i10; -import 'package:cocoon_service/src/model/ci_yaml/target.dart' as _i27; -import 'package:cocoon_service/src/model/commit_ref.dart' as _i31; -import 'package:cocoon_service/src/model/firestore/task.dart' as _i32; -import 'package:cocoon_service/src/model/github/checks.dart' as _i30; +import 'package:cocoon_service/src/model/ci_yaml/target.dart' as _i28; +import 'package:cocoon_service/src/model/commit_ref.dart' as _i32; +import 'package:cocoon_service/src/model/firestore/task.dart' as _i33; +import 'package:cocoon_service/src/model/github/checks.dart' as _i31; import 'package:cocoon_service/src/service/big_query.dart' as _i18; import 'package:cocoon_service/src/service/commit_service.dart' as _i21; import 'package:cocoon_service/src/service/config.dart' as _i2; -import 'package:cocoon_service/src/service/discord_service.dart' as _i24; +import 'package:cocoon_service/src/service/discord_service.dart' as _i25; +import 'package:cocoon_service/src/service/flags/dynamic_config.dart' as _i24; import 'package:cocoon_service/src/service/github_service.dart' as _i9; import 'package:cocoon_service/src/service/luci_build_service/build_tags.dart' - as _i35; + as _i36; import 'package:cocoon_service/src/service/luci_build_service/cipd_version.dart' as _i23; import 'package:cocoon_service/src/service/luci_build_service/engine_artifacts.dart' - as _i28; + as _i29; import 'package:cocoon_service/src/service/luci_build_service/pending_task.dart' - as _i34; + as _i35; import 'package:cocoon_service/src/service/luci_build_service/user_data.dart' - as _i29; -import 'package:fixnum/fixnum.dart' as _i33; + as _i30; +import 'package:fixnum/fixnum.dart' as _i34; import 'package:github/github.dart' as _i7; import 'package:github/hooks.dart' as _i22; import 'package:googleapis/bigquery/v2.dart' as _i4; @@ -43,9 +44,9 @@ import 'package:http/http.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i20; import 'package:neat_cache/neat_cache.dart' as _i16; -import 'package:process/src/interface/process_manager.dart' as _i36; +import 'package:process/src/interface/process_manager.dart' as _i37; -import '../../service/cache_service_test.dart' as _i25; +import '../../service/cache_service_test.dart' as _i26; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -736,17 +737,6 @@ class MockConfig extends _i1.Mock implements _i2.Config { ) as Set<_i7.RepositorySlug>); - @override - _i2.DynamicConfig get flags => - (super.noSuchMethod( - Invocation.getter(#flags), - returnValue: _i20.dummyValue<_i2.DynamicConfig>( - this, - Invocation.getter(#flags), - ), - ) - as _i2.DynamicConfig); - @override Set<_i7.RepositorySlug> get supportedRepos => (super.noSuchMethod( @@ -1093,6 +1083,17 @@ class MockConfig extends _i1.Mock implements _i2.Config { ) as Set); + @override + _i24.DynamicConfig get flags => + (super.noSuchMethod( + Invocation.getter(#flags), + returnValue: _i20.dummyValue<_i24.DynamicConfig>( + this, + Invocation.getter(#flags), + ), + ) + as _i24.DynamicConfig); + @override String wrongHeadBranchPullRequestMessage(String? branch) => (super.noSuchMethod( @@ -1250,54 +1251,54 @@ class MockConfig extends _i1.Mock implements _i2.Config { /// A class which mocks [DiscordService]. /// /// See the documentation for Mockito's code generation for more information. -class MockDiscordService extends _i1.Mock implements _i24.DiscordService { +class MockDiscordService extends _i1.Mock implements _i25.DiscordService { MockDiscordService() { _i1.throwOnMissingStub(this); } @override - _i13.Future<_i24.DiscordStatus> postTreeStatusMessage(String? message) => + _i13.Future<_i25.DiscordStatus> postTreeStatusMessage(String? message) => (super.noSuchMethod( Invocation.method(#postTreeStatusMessage, [message]), - returnValue: _i13.Future<_i24.DiscordStatus>.value( - _i24.DiscordStatus.ok, + returnValue: _i13.Future<_i25.DiscordStatus>.value( + _i25.DiscordStatus.ok, ), ) - as _i13.Future<_i24.DiscordStatus>); + as _i13.Future<_i25.DiscordStatus>); } /// A class which mocks [FakeEntry]. /// /// See the documentation for Mockito's code generation for more information. -class MockFakeEntry extends _i1.Mock implements _i25.FakeEntry { +class MockFakeEntry extends _i1.Mock implements _i26.FakeEntry { MockFakeEntry() { _i1.throwOnMissingStub(this); } @override - _i26.Uint8List get value => + _i27.Uint8List get value => (super.noSuchMethod( Invocation.getter(#value), - returnValue: _i26.Uint8List(0), + returnValue: _i27.Uint8List(0), ) - as _i26.Uint8List); + as _i27.Uint8List); @override - set value(_i26.Uint8List? _value) => super.noSuchMethod( + set value(_i27.Uint8List? _value) => super.noSuchMethod( Invocation.setter(#value, _value), returnValueForMissingStub: null, ); @override - _i13.Future<_i26.Uint8List> get([ - _i13.Future<_i26.Uint8List?> Function()? create, + _i13.Future<_i27.Uint8List> get([ + _i13.Future<_i27.Uint8List?> Function()? create, Duration? ttl, ]) => (super.noSuchMethod( Invocation.method(#get, [create, ttl]), - returnValue: _i13.Future<_i26.Uint8List>.value(_i26.Uint8List(0)), + returnValue: _i13.Future<_i27.Uint8List>.value(_i27.Uint8List(0)), ) - as _i13.Future<_i26.Uint8List>); + as _i13.Future<_i27.Uint8List>); @override _i13.Future purge({int? retries = 0}) => @@ -1309,12 +1310,12 @@ class MockFakeEntry extends _i1.Mock implements _i25.FakeEntry { as _i13.Future); @override - _i13.Future<_i26.Uint8List?> set(_i26.Uint8List? value, [Duration? ttl]) => + _i13.Future<_i27.Uint8List?> set(_i27.Uint8List? value, [Duration? ttl]) => (super.noSuchMethod( Invocation.method(#set, [value, ttl]), - returnValue: _i13.Future<_i26.Uint8List?>.value(), + returnValue: _i13.Future<_i27.Uint8List?>.value(), ) - as _i13.Future<_i26.Uint8List?>); + as _i13.Future<_i27.Uint8List?>); } /// A class which mocks [IssuesService]. @@ -4038,10 +4039,10 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { as _i13.Future>); @override - _i13.Future> scheduleTryBuilds({ - required List<_i27.Target>? targets, + _i13.Future> scheduleTryBuilds({ + required List<_i28.Target>? targets, required _i7.PullRequest? pullRequest, - required _i28.EngineArtifacts? engineArtifacts, + required _i29.EngineArtifacts? engineArtifacts, }) => (super.noSuchMethod( Invocation.method(#scheduleTryBuilds, [], { @@ -4049,9 +4050,9 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { #pullRequest: pullRequest, #engineArtifacts: engineArtifacts, }), - returnValue: _i13.Future>.value(<_i27.Target>[]), + returnValue: _i13.Future>.value(<_i28.Target>[]), ) - as _i13.Future>); + as _i13.Future>); @override _i13.Future cancelBuilds({ @@ -4088,7 +4089,7 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { required String? builderName, required _i6.Build? build, required int? nextAttempt, - required _i29.PresubmitUserData? userData, + required _i30.PresubmitUserData? userData, }) => (super.noSuchMethod( Invocation.method(#reschedulePresubmitBuild, [], { @@ -4113,10 +4114,10 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { @override _i13.Future reschedulePostsubmitBuildUsingCheckRunEvent( - _i30.CheckRunEvent? checkRunEvent, { - required _i31.CommitRef? commit, - required _i27.Target? target, - required _i32.Task? task, + _i31.CheckRunEvent? checkRunEvent, { + required _i32.CommitRef? commit, + required _i28.Target? target, + required _i33.Task? task, }) => (super.noSuchMethod( Invocation.method( @@ -4131,7 +4132,7 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { @override _i13.Future<_i6.Build> getBuildById( - _i33.Int64? id, { + _i34.Int64? id, { _i6.BuildMask? buildMask, }) => (super.noSuchMethod( @@ -4160,25 +4161,25 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { as _i13.Future>); @override - _i13.Future> schedulePostsubmitBuilds({ - required _i31.CommitRef? commit, - required List<_i34.PendingTask>? toBeScheduled, + _i13.Future> schedulePostsubmitBuilds({ + required _i32.CommitRef? commit, + required List<_i35.PendingTask>? toBeScheduled, }) => (super.noSuchMethod( Invocation.method(#schedulePostsubmitBuilds, [], { #commit: commit, #toBeScheduled: toBeScheduled, }), - returnValue: _i13.Future>.value( - <_i34.PendingTask>[], + returnValue: _i13.Future>.value( + <_i35.PendingTask>[], ), ) - as _i13.Future>); + as _i13.Future>); @override _i13.Future scheduleMergeGroupBuilds({ - required _i31.CommitRef? commit, - required List<_i27.Target>? targets, + required _i32.CommitRef? commit, + required List<_i28.Target>? targets, }) => (super.noSuchMethod( Invocation.method(#scheduleMergeGroupBuilds, [], { @@ -4192,8 +4193,8 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { @override _i13.Future<_i7.CheckRun> createPostsubmitCheckRun( - _i31.CommitRef? commit, - _i27.Target? target, + _i32.CommitRef? commit, + _i28.Target? target, ) => (super.noSuchMethod( Invocation.method(#createPostsubmitCheckRun, [commit, target]), @@ -4208,10 +4209,10 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { @override _i13.Future rerunBuilder({ - required _i31.CommitRef? commit, - required _i27.Target? target, - required _i32.Task? task, - Iterable<_i35.BuildTag>? tags = const [], + required _i32.CommitRef? commit, + required _i28.Target? target, + required _i33.Task? task, + Iterable<_i36.BuildTag>? tags = const [], }) => (super.noSuchMethod( Invocation.method(#rerunBuilder, [], { @@ -4226,8 +4227,8 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { @override _i13.Future rerunDartInternalReleaseBuilder({ - required _i31.CommitRef? commit, - required _i32.Task? task, + required _i32.CommitRef? commit, + required _i33.Task? task, }) => (super.noSuchMethod( Invocation.method(#rerunDartInternalReleaseBuilder, [], { @@ -4242,7 +4243,7 @@ class MockLuciBuildService extends _i1.Mock implements _i17.LuciBuildService { /// A class which mocks [ProcessManager]. /// /// See the documentation for Mockito's code generation for more information. -class MockProcessManager extends _i1.Mock implements _i36.ProcessManager { +class MockProcessManager extends _i1.Mock implements _i37.ProcessManager { MockProcessManager() { _i1.throwOnMissingStub(this); } @@ -5395,35 +5396,35 @@ class MockPullRequestLabelProcessor extends _i1.Mock /// A class which mocks [Cache]. /// /// See the documentation for Mockito's code generation for more information. -class MockCache extends _i1.Mock implements _i16.Cache<_i26.Uint8List> { +class MockCache extends _i1.Mock implements _i16.Cache<_i27.Uint8List> { MockCache() { _i1.throwOnMissingStub(this); } @override - _i16.Entry<_i26.Uint8List> operator [](String? key) => + _i16.Entry<_i27.Uint8List> operator [](String? key) => (super.noSuchMethod( Invocation.method(#[], [key]), - returnValue: _FakeEntry_58<_i26.Uint8List>( + returnValue: _FakeEntry_58<_i27.Uint8List>( this, Invocation.method(#[], [key]), ), ) - as _i16.Entry<_i26.Uint8List>); + as _i16.Entry<_i27.Uint8List>); @override - _i16.Cache<_i26.Uint8List> withPrefix(String? prefix) => + _i16.Cache<_i27.Uint8List> withPrefix(String? prefix) => (super.noSuchMethod( Invocation.method(#withPrefix, [prefix]), - returnValue: _FakeCache_59<_i26.Uint8List>( + returnValue: _FakeCache_59<_i27.Uint8List>( this, Invocation.method(#withPrefix, [prefix]), ), ) - as _i16.Cache<_i26.Uint8List>); + as _i16.Cache<_i27.Uint8List>); @override - _i16.Cache withCodec(_i12.Codec? codec) => + _i16.Cache withCodec(_i12.Codec? codec) => (super.noSuchMethod( Invocation.method(#withCodec, [codec]), returnValue: _FakeCache_59( @@ -5434,13 +5435,13 @@ class MockCache extends _i1.Mock implements _i16.Cache<_i26.Uint8List> { as _i16.Cache); @override - _i16.Cache<_i26.Uint8List> withTTL(Duration? ttl) => + _i16.Cache<_i27.Uint8List> withTTL(Duration? ttl) => (super.noSuchMethod( Invocation.method(#withTTL, [ttl]), - returnValue: _FakeCache_59<_i26.Uint8List>( + returnValue: _FakeCache_59<_i27.Uint8List>( this, Invocation.method(#withTTL, [ttl]), ), ) - as _i16.Cache<_i26.Uint8List>); + as _i16.Cache<_i27.Uint8List>); } diff --git a/app_dart/tool/local_server.dart b/app_dart/tool/local_server.dart index e627a7a238..6f581a5a9b 100644 --- a/app_dart/tool/local_server.dart +++ b/app_dart/tool/local_server.dart @@ -15,6 +15,7 @@ import 'package:cocoon_service/src/service/big_query.dart'; import 'package:cocoon_service/src/service/build_status_service.dart'; import 'package:cocoon_service/src/service/commit_service.dart'; import 'package:cocoon_service/src/service/firebase_jwt_validator.dart'; +import 'package:cocoon_service/src/service/flags/dynamic_config.dart'; import 'package:cocoon_service/src/service/get_files_changed.dart'; import 'package:cocoon_service/src/service/scheduler/ci_yaml_fetcher.dart'; @@ -26,7 +27,7 @@ Future main() async { final config = Config( cache, FakeSecretManager(), - dynamicConfig: DynamicConfig.fromJson({}), + initialConfig: DynamicConfig.fromJson({}), ); final firestore = FakeFirestoreService();