[codesign] add function to check notarization status (#2107)
diff --git a/codesign/lib/src/file_codesign_visitor.dart b/codesign/lib/src/file_codesign_visitor.dart
index 486c918..7ebc23d 100644
--- a/codesign/lib/src/file_codesign_visitor.dart
+++ b/codesign/lib/src/file_codesign_visitor.dart
@@ -90,6 +90,8 @@
</dict>
</plist>
''';
+ static const Duration _notarizationTimerDuration = Duration(seconds: 45);
+ static final RegExp _notarytoolStatusCheckPattern = RegExp(r'[ ]*status: ([a-zA-z ]+)');
static const String fixItInstructions = '''
Codesign test failed.
@@ -245,4 +247,76 @@
fileWithEntitlements.addAll(await fileSystem.file(entitlementFilePath).readAsLines());
return fileWithEntitlements;
}
+
+ /// Upload a zip archive to the notary service and verify the build succeeded.
+ ///
+ /// The apple notarization service will unzip the artifact, validate all
+ /// binaries are properly codesigned, and notarize the entire archive.
+ Future<void> notarize(File file) async {
+ final Completer<void> completer = Completer<void>();
+ final String uuid = uploadZipToNotary(file);
+
+ Future<void> callback(Timer timer) async {
+ final bool notaryFinished = checkNotaryJobFinished(uuid);
+ if (notaryFinished) {
+ timer.cancel();
+ log.info('successfully notarized ${file.path}');
+ completer.complete();
+ }
+ }
+
+ // check on results
+ Timer.periodic(
+ _notarizationTimerDuration,
+ callback,
+ );
+ await completer.future;
+ }
+
+ String uploadZipToNotary(File localFile, [int retryCount = 3]) {
+ throw UnimplementedError('will implement later');
+ }
+
+ /// Make a request to the notary service to see if the notary job is finished.
+ ///
+ /// A return value of true means that notarization finished successfully,
+ /// false means that the job is still pending. If the notarization fails, this
+ /// function will throw a [ConductorException].
+ bool checkNotaryJobFinished(String uuid) {
+ final List<String> args = <String>[
+ 'xcrun',
+ 'notarytool',
+ 'info',
+ uuid,
+ '--password',
+ appSpecificPassword,
+ '--apple-id',
+ codesignAppstoreId,
+ '--team-id',
+ codesignTeamId,
+ ];
+
+ log.info('checking notary status with ${args.join(' ')}');
+ final io.ProcessResult result = processManager.runSync(args);
+ final String combinedOutput = (result.stdout as String) + (result.stderr as String);
+
+ final RegExpMatch? match = _notarytoolStatusCheckPattern.firstMatch(combinedOutput);
+
+ if (match == null) {
+ throw CodesignException(
+ 'Malformed output from "${args.join(' ')}"\n${combinedOutput.trim()}',
+ );
+ }
+
+ final String status = match.group(1)!;
+
+ if (status == 'Accepted') {
+ return true;
+ }
+ if (status == 'In Progress') {
+ log.info('job $uuid still pending');
+ return false;
+ }
+ throw CodesignException('Notarization failed with: $status\n$combinedOutput');
+ }
}
diff --git a/codesign/test/file_codesign_visitor_test.dart b/codesign/test/file_codesign_visitor_test.dart
index 5ee2a79..4890a24 100644
--- a/codesign/test/file_codesign_visitor_test.dart
+++ b/codesign/test/file_codesign_visitor_test.dart
@@ -460,4 +460,144 @@
));
});
});
+
+ group('notarization tests: ', () {
+ setUp(() {
+ tempDir = fileSystem.systemTempDirectory.createTempSync('conductor_codesign');
+ processManager = FakeProcessManager.list(<FakeCommand>[]);
+ codesignVisitor = cs.FileCodesignVisitor(
+ codesignCertName: randomString,
+ codesignUserName: randomString,
+ appSpecificPassword: randomString,
+ codesignAppstoreId: randomString,
+ codesignTeamId: randomString,
+ codesignFilepaths: fakeFilepaths,
+ commitHash: randomString,
+ fileSystem: fileSystem,
+ processManager: processManager,
+ tempDir: tempDir,
+ );
+ codesignVisitor.directoriesVisited.clear();
+ records.clear();
+ log.onRecord.listen((LogRecord record) => records.add(record));
+ });
+
+ test('successful notarization check returns true', () async {
+ processManager.addCommands(<FakeCommand>[
+ const FakeCommand(
+ command: <String>[
+ 'xcrun',
+ 'notarytool',
+ 'info',
+ randomString,
+ '--password',
+ randomString,
+ '--apple-id',
+ randomString,
+ '--team-id',
+ randomString,
+ ],
+ stdout: '''createdDate: 2021-04-29T01:38:09.498Z
+id: 2efe2717-52ef-43a5-96dc-0797e4ca1041
+name: OvernightTextEditor_11.6.8.zip
+status: Accepted''',
+ ),
+ ]);
+
+ expect(
+ codesignVisitor.checkNotaryJobFinished(randomString),
+ true,
+ );
+ });
+
+ test('wrong format (such as altool) check throws exception', () async {
+ processManager.addCommands(<FakeCommand>[
+ const FakeCommand(
+ command: <String>[
+ 'xcrun',
+ 'notarytool',
+ 'info',
+ randomString,
+ '--password',
+ randomString,
+ '--apple-id',
+ randomString,
+ '--team-id',
+ randomString,
+ ],
+ stdout: '''RequestUUID: 2EFE2717-52EF-43A5-96DC-0797E4CA1041
+Date: 2021-07-02 20:32:01 +0000
+Status: invalid
+LogFileURL: https://osxapps.itunes.apple.com/...
+Status Code: 2
+Status Message: Package Invalid''',
+ ),
+ ]);
+
+ expect(
+ () => codesignVisitor.checkNotaryJobFinished(randomString),
+ throwsA(
+ isA<CodesignException>(),
+ ),
+ );
+ });
+
+ test('in progress notarization check returns false', () async {
+ processManager.addCommands(<FakeCommand>[
+ const FakeCommand(
+ command: <String>[
+ 'xcrun',
+ 'notarytool',
+ 'info',
+ randomString,
+ '--password',
+ randomString,
+ '--apple-id',
+ randomString,
+ '--team-id',
+ randomString,
+ ],
+ stdout: '''createdDate: 2021-04-29T01:38:09.498Z
+id: 2efe2717-52ef-43a5-96dc-0797e4ca1041
+name: OvernightTextEditor_11.6.8.zip
+status: In Progress''',
+ ),
+ ]);
+
+ expect(
+ codesignVisitor.checkNotaryJobFinished(randomString),
+ false,
+ );
+ });
+
+ test('invalid status check throws exception', () async {
+ processManager.addCommands(<FakeCommand>[
+ const FakeCommand(
+ command: <String>[
+ 'xcrun',
+ 'notarytool',
+ 'info',
+ randomString,
+ '--password',
+ randomString,
+ '--apple-id',
+ randomString,
+ '--team-id',
+ randomString,
+ ],
+ stdout: '''createdDate: 2021-04-29T01:38:09.498Z
+id: 2efe2717-52ef-43a5-96dc-0797e4ca1041
+name: OvernightTextEditor_11.6.8.zip
+status: Invalid''',
+ ),
+ ]);
+
+ expect(
+ () => codesignVisitor.checkNotaryJobFinished(randomString),
+ throwsA(
+ isA<CodesignException>(),
+ ),
+ );
+ });
+ });
}