Skip to content

Commit f8367b4

Browse files
authored
Merge branch 'master' into export-api-api-exporter-final
2 parents ec3c8ab + 0346f4d commit f8367b4

File tree

13 files changed

+151
-43
lines changed

13 files changed

+151
-43
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ Important changes to data models, configuration, and migrations between each
22
AppEngine version, listed here to ease deployment and troubleshooting.
33

44
## Next Release (replace with git tag when deployed)
5+
6+
## `20241111t124500-all`
57
* Note: `search` increased `description` weight from `0.9` to `1.0`.
6-
* Bump runtimeVersion to `2024.11.08`.
8+
* Bump runtimeVersion to `2024.11.11`.
9+
* Enabled generation of `ExportedApi`.
710

811
## `20241107t132800-all`
912
* `search` uses the `IndexedScore` to reduce memory allocations.

app/config/dartlang-pub.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ fallbackSearchServicePrefix: https://search-dot-dartlang-pub.appspot.com
1212
defaultServiceBaseUrl: https://{{GAE_VERSION}}-dot-{{GOOGLE_CLOUD_PROJECT}}.appspot.com
1313
popularityDumpBucketName: dartlang-pub--popularity
1414
searchSnapshotBucketName: dartlang-pub--search-snapshot
15-
exportedApiBucketName: dartlang-pub-exported-api
15+
exportedApiBucketName: null # TODO: dartlang-pub-exported-api
1616
maxTaskInstances: 700
1717
maxTaskRunHours: 2
1818
taskResultBucketName: dartlang-pub-task-output

app/lib/admin/actions/actions.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:pub_dev/admin/actions/exported-api-sync.dart';
6+
57
import '../../shared/exceptions.dart';
68
import 'download_counts_backfill.dart';
79
import 'download_counts_delete.dart';
@@ -92,6 +94,7 @@ final class AdminAction {
9294
downloadCountsBackfill,
9395
downloadCountsDelete,
9496
emailSend,
97+
exportedApiSync,
9598
mergeModeratedPackageIntoExisting,
9699
moderatePackage,
97100
moderatePackageVersion,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:pub_dev/package/api_export/api_exporter.dart';
6+
7+
import 'actions.dart';
8+
9+
final exportedApiSync = AdminAction(
10+
name: 'exported-api-sync',
11+
summary: 'Synchronize exported API bucket',
12+
description: '''
13+
This command will trigger synchronization of exported API bucket.
14+
15+
This is the bucket from which API responses are served.
16+
17+
This synchronize packages specified with:
18+
--packages="foo bar"
19+
20+
Synchronize all packages with:
21+
--packages=ALL
22+
23+
Optionally, write rewrite of all files using:
24+
--force-write=true
25+
''',
26+
options: {
27+
'packages': 'Space separated list of packages, use "ALL" for all!',
28+
'force-write': 'true/false if writes should be forced (default: false)',
29+
},
30+
invoke: (options) async {
31+
final forceWriteString = options['force-write'] ?? 'false';
32+
InvalidInputException.checkAnyOf(
33+
forceWriteString,
34+
'force-write',
35+
['true', 'false'],
36+
);
37+
final forceWrite = forceWriteString == 'true';
38+
39+
final packagesOpt = options['packages'] ?? '';
40+
final syncAll = packagesOpt == 'ALL';
41+
if (syncAll) {
42+
await apiExporter!.synchronizeExportedApi(forceWrite: forceWrite);
43+
return {
44+
'packages': 'ALL',
45+
'forceWrite': forceWrite,
46+
};
47+
} else {
48+
final packages = packagesOpt.split(' ').map((p) => p.trim()).toList();
49+
for (final p in packages) {
50+
InvalidInputException.checkPackageName(p);
51+
}
52+
for (final p in packages) {
53+
await apiExporter!.synchronizePackage(p, forceWrite: forceWrite);
54+
}
55+
return {
56+
'packages': packages,
57+
'forceWrite': forceWrite,
58+
};
59+
}
60+
},
61+
);

app/lib/package/api_export/api_exporter.dart

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ ApiExporter? get apiExporter => ss.lookup(#_apiExporter) as ApiExporter?;
3333
const _concurrency = 50;
3434

3535
final class ApiExporter {
36+
final DatastoreDB _db;
37+
3638
final ExportedApi _api;
3739

3840
/// If [stop] has been called to stop background processes.
@@ -46,7 +48,8 @@ final class ApiExporter {
4648
/// `null` when not started yet.
4749
Completer<void>? _stopped;
4850

49-
ApiExporter({
51+
ApiExporter(
52+
this._db, {
5053
required Bucket bucket,
5154
}) : _api = ExportedApi(storageService, bucket);
5255

@@ -122,18 +125,21 @@ final class ApiExporter {
122125
}
123126

124127
/// Gets and uploads the package name completion data.
125-
Future<void> synchronizePackageNameCompletionData() async {
128+
Future<void> synchronizePackageNameCompletionData({
129+
bool forceWrite = false,
130+
}) async {
126131
await _api.packageNameCompletionData.write(
127132
await searchBackend.getPackageNameCompletionData(),
133+
forceWrite: forceWrite,
128134
);
129135
}
130136

131137
/// Synchronize all exported API.
132138
///
133139
/// This is intended to be scheduled from a daily background task.
134-
Future<void> synchronizeExportedApi() async {
140+
Future<void> synchronizeExportedApi({bool forceWrite = false}) async {
135141
final allPackageNames = <String>{};
136-
final packageQuery = dbService.query<Package>();
142+
final packageQuery = _db.query<Package>();
137143
var errCount = 0;
138144
await packageQuery.run().parallelForEach(_concurrency, (pkg) async {
139145
final name = pkg.name!;
@@ -143,13 +149,13 @@ final class ApiExporter {
143149
allPackageNames.add(name);
144150

145151
// TODO: Consider retries around all this logic
146-
await synchronizePackage(name);
152+
await synchronizePackage(name, forceWrite: forceWrite);
147153
}, onError: (e, st) {
148154
_log.warning('synchronizePackage() failed', e, st);
149155
errCount++;
150156
});
151157

152-
await synchronizePackageNameCompletionData();
158+
await synchronizePackageNameCompletionData(forceWrite: forceWrite);
153159

154160
await _api.notFound.write({
155161
'error': {
@@ -158,7 +164,7 @@ final class ApiExporter {
158164
},
159165
'code': 'NotFound',
160166
'message': 'Package or version requested could not be found.',
161-
});
167+
}, forceWrite: forceWrite);
162168

163169
await _api.garbageCollect(allPackageNames);
164170

@@ -182,7 +188,10 @@ final class ApiExporter {
182188
/// * Running a full background synchronization.
183189
/// * When a change in [Package.updated] is detected.
184190
/// * A package is moderated, or other admin action is applied.
185-
Future<void> synchronizePackage(String package) async {
191+
Future<void> synchronizePackage(
192+
String package, {
193+
bool forceWrite = false,
194+
}) async {
186195
_log.info('synchronizePackage("$package")');
187196

188197
final PackageData versionListing;
@@ -212,9 +221,18 @@ final class ApiExporter {
212221
(version, _) => !versionListing.versions.any((v) => v.version == version),
213222
);
214223

215-
await _api.package(package).synchronizeTarballs(versions);
216-
await _api.package(package).advisories.write(advisories);
217-
await _api.package(package).versions.write(versionListing);
224+
await _api.package(package).synchronizeTarballs(
225+
versions,
226+
forceWrite: forceWrite,
227+
);
228+
await _api.package(package).advisories.write(
229+
advisories,
230+
forceWrite: forceWrite,
231+
);
232+
await _api.package(package).versions.write(
233+
versionListing,
234+
forceWrite: forceWrite,
235+
);
218236
}
219237

220238
/// Scan for updates from packages until [abort] is resolved, or [claim]
@@ -236,7 +254,7 @@ final class ApiExporter {
236254
var since = clock.ago(days: 3);
237255
while (claim.valid && !abort.isCompleted) {
238256
// Look at all packages changed in [since]
239-
final q = dbService.query<Package>()
257+
final q = _db.query<Package>()
240258
..filter('updated >', since)
241259
..order('-updated');
242260

app/lib/package/api_export/exported_api.dart

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,16 @@ final class ExportedPackage {
284284
///
285285
/// This method will copy GCS objects, when necessary, relying on
286286
/// [SourceObjectInfo.md5Hash] to avoid copying objects that haven't changed.
287+
/// If [forceWrite] is given all files will be rewritten ignoring their
288+
/// previous state.
287289
///
288290
/// [versions] **must** have an entry for each version that exists.
289291
/// This will **delete** tarballs for versions that do not exist in
290292
/// [versions].
291293
Future<void> synchronizeTarballs(
292-
Map<String, SourceObjectInfo> versions,
293-
) async {
294+
Map<String, SourceObjectInfo> versions, {
295+
bool forceWrite = false,
296+
}) async {
294297
await Future.wait([
295298
..._owner._prefixes.map((prefix) async {
296299
final pfx = '$prefix/api/archives/$_package-';
@@ -314,7 +317,7 @@ final class ExportedPackage {
314317
);
315318

316319
final info = versions[version];
317-
if (info != null) {
320+
if (info != null && !forceWrite) {
318321
await tarball(version)._copyToPrefixFromIfNotContentEquals(
319322
prefix,
320323
info,
@@ -342,6 +345,7 @@ final class ExportedPackage {
342345
prefix,
343346
versions[v]!,
344347
null,
348+
forceWrite: forceWrite,
345349
);
346350
});
347351
}));
@@ -495,7 +499,10 @@ final class ExportedJsonFile<T> extends ExportedObject {
495499
}
496500

497501
/// Write [data] as gzipped JSON in UTF-8 format.
498-
Future<void> write(T data) async {
502+
///
503+
/// This will only write of `Content-Length` and `md5Hash` doesn't match the
504+
/// existing file, or if [forceWrite] is given.
505+
Future<void> write(T data, {bool forceWrite = false}) async {
499506
final gzipped = _jsonGzip.encode(data);
500507
final metadata = _metadata();
501508

@@ -505,6 +512,7 @@ final class ExportedJsonFile<T> extends ExportedObject {
505512
prefix + _objectName,
506513
gzipped,
507514
metadata,
515+
forceWrite: forceWrite,
508516
);
509517
});
510518
}));
@@ -542,14 +550,18 @@ final class ExportedBlob extends ExportedObject {
542550
}
543551

544552
/// Write binary blob to this file.
545-
Future<void> write(List<int> data) async {
553+
///
554+
/// This will only write of `Content-Length` and `md5Hash` doesn't match the
555+
/// existing file, or if [forceWrite] is given.
556+
Future<void> write(List<int> data, {bool forceWrite = false}) async {
546557
final metadata = _metadata();
547558
await Future.wait(_owner._prefixes.map((prefix) async {
548559
await _owner._pool.withResource(() async {
549560
await _owner._bucket.writeBytesIfDifferent(
550561
prefix + _objectName,
551562
data,
552563
metadata,
564+
forceWrite: forceWrite,
553565
);
554566
});
555567
}));
@@ -563,7 +575,11 @@ final class ExportedBlob extends ExportedObject {
563575
/// [source] is required to be [SourceObjectInfo] for the source object.
564576
/// This method will use [ObjectInfo.length] and [ObjectInfo.md5Hash] to
565577
/// determine if it's necessary to copy the object.
566-
Future<void> copyFrom(SourceObjectInfo source) async {
578+
/// If [forceWrite] is given, this method will always copy the object.
579+
Future<void> copyFrom(
580+
SourceObjectInfo source, {
581+
bool forceWrite = false,
582+
}) async {
567583
await Future.wait(_owner._prefixes.map((prefix) async {
568584
await _owner._pool.withResource(() async {
569585
final dst = prefix + _objectName;
@@ -572,6 +588,7 @@ final class ExportedBlob extends ExportedObject {
572588
prefix,
573589
source,
574590
await _owner._bucket.tryInfo(dst),
591+
forceWrite: forceWrite,
575592
);
576593
});
577594
}));
@@ -592,12 +609,13 @@ final class ExportedBlob extends ExportedObject {
592609
Future<void> _copyToPrefixFromIfNotContentEquals(
593610
String prefix,
594611
SourceObjectInfo source,
595-
ObjectInfo? destinationInfo,
596-
) async {
612+
ObjectInfo? destinationInfo, {
613+
bool forceWrite = false,
614+
}) async {
597615
final dst = prefix + _objectName;
598616

599617
// Check if the dst already exists
600-
if (destinationInfo != null) {
618+
if (destinationInfo != null && !forceWrite) {
601619
if (destinationInfo.name != dst) {
602620
throw ArgumentError.value(
603621
destinationInfo,
@@ -633,15 +651,18 @@ extension on Bucket {
633651
Future<void> writeBytesIfDifferent(
634652
String name,
635653
List<int> bytes,
636-
ObjectMetadata metadata,
637-
) async {
638-
if (await tryInfo(name) case final info?) {
639-
if (info.isSameContent(bytes)) {
640-
if (info.metadata.validated
641-
.isBefore(clock.agoBy(_updateValidatedAfter))) {
642-
await updateMetadata(name, metadata);
654+
ObjectMetadata metadata, {
655+
bool forceWrite = false,
656+
}) async {
657+
if (!forceWrite) {
658+
if (await tryInfo(name) case final info?) {
659+
if (info.isSameContent(bytes)) {
660+
if (info.metadata.validated
661+
.isBefore(clock.agoBy(_updateValidatedAfter))) {
662+
await updateMetadata(name, metadata);
663+
}
664+
return;
643665
}
644-
return;
645666
}
646667
}
647668

app/lib/service/services.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,10 @@ Future<R> _withPubServices<R>(FutureOr<R> Function() fn) async {
245245
registerAnnouncementBackend(AnnouncementBackend());
246246
if (activeConfiguration.exportedApiBucketName != null) {
247247
registerApiExporter(ApiExporter(
248-
bucket: storageService
249-
.bucket(activeConfiguration.exportedApiBucketName!)));
248+
dbService,
249+
bucket:
250+
storageService.bucket(activeConfiguration.exportedApiBucketName!),
251+
));
250252
}
251253
registerAsyncQueue(AsyncQueue());
252254
registerAuditBackend(AuditBackend(dbService));

app/lib/shared/versions.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ final RegExp runtimeVersionPattern = RegExp(r'^\d{4}\.\d{2}\.\d{2}$');
2424
/// when the version switch happens.
2525
const _acceptedRuntimeVersions = <String>[
2626
// The current [runtimeVersion].
27-
'2024.11.08',
27+
'2024.11.11',
2828
// Fallback runtime versions.
2929
'2024.10.29',
3030
'2024.10.21',

app/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies:
1717
fake_async: ^1.2.0
1818
fake_gcloud:
1919
path: ../pkg/fake_gcloud
20-
gcloud: ^0.8.16
20+
gcloud: ^0.8.17
2121
googleapis: ^13.0.0
2222
googleapis_auth: ^1.1.0
2323
html: ^0.15.0

0 commit comments

Comments
 (0)