[codesign] the semi entry point of visiting files (#2125)

diff --git a/codesign/bin/codesign.dart b/codesign/bin/codesign.dart
index 84bdbd4..ec88c0d 100644
--- a/codesign/bin/codesign.dart
+++ b/codesign/bin/codesign.dart
@@ -104,8 +104,13 @@
   }
 
   const FileSystem fileSystem = LocalFileSystem();
-  final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('conductor_codesign');
+  final Directory rootDirectory = fileSystem.systemTempDirectory.createTempSync('conductor_codesign');
   const ProcessManager processManager = LocalProcessManager();
+  final GoogleCloudStorage googleCloudStorage = GoogleCloudStorage(
+    processManager: processManager,
+    rootDirectory: rootDirectory,
+    commitHash: commit,
+  );
 
   return FileCodesignVisitor(
     codesignCertName: codesignCertName,
@@ -116,8 +121,9 @@
     codesignTeamId: codesignTeamId,
     codesignFilepaths: codesignFilepaths,
     fileSystem: fileSystem,
-    tempDir: tempDir,
+    rootDirectory: rootDirectory,
     processManager: processManager,
     production: production,
+    googleCloudStorage: googleCloudStorage,
   ).validateAll();
 }
diff --git a/codesign/lib/codesign.dart b/codesign/lib/codesign.dart
index 54fe0c8..b814211 100644
--- a/codesign/lib/codesign.dart
+++ b/codesign/lib/codesign.dart
@@ -3,4 +3,5 @@
 // found in the LICENSE file.
 
 export 'src/file_codesign_visitor.dart';
+export 'src/google_cloud_storage.dart';
 export 'src/utils.dart';
diff --git a/codesign/lib/src/file_codesign_visitor.dart b/codesign/lib/src/file_codesign_visitor.dart
index 56f7d1e..f476a5f 100644
--- a/codesign/lib/src/file_codesign_visitor.dart
+++ b/codesign/lib/src/file_codesign_visitor.dart
@@ -8,6 +8,7 @@
 import 'package:file/file.dart';
 import 'package:process/process.dart';
 
+import 'google_cloud_storage.dart';
 import 'log.dart';
 import 'utils.dart';
 
@@ -32,21 +33,24 @@
     required this.codesignTeamId,
     required this.codesignFilepaths,
     required this.fileSystem,
-    required this.tempDir,
+    required this.rootDirectory,
     required this.processManager,
+    required this.googleCloudStorage,
     this.production = false,
+    this.notarizationTimerDuration = const Duration(seconds: 5),
   }) {
-    entitlementsFile = tempDir.childFile('Entitlements.plist')..writeAsStringSync(_entitlementsFileContents);
-    remoteDownloadsDir = tempDir.childDirectory('downloads')..createSync();
-    codesignedZipsDir = tempDir.childDirectory('codesigned_zips')..createSync();
+    entitlementsFile = rootDirectory.childFile('Entitlements.plist')..writeAsStringSync(_entitlementsFileContents);
+    remoteDownloadsDir = rootDirectory.childDirectory('downloads')..createSync();
+    codesignedZipsDir = rootDirectory.childDirectory('codesigned_zips')..createSync();
   }
 
   /// Temp [Directory] to download/extract files to.
   ///
   /// This file will be deleted if [validateAll] completes successfully.
-  final Directory tempDir;
+  final Directory rootDirectory;
   final FileSystem fileSystem;
   final ProcessManager processManager;
+  final GoogleCloudStorage googleCloudStorage;
 
   final String commitHash;
   final String codesignCertName;
@@ -55,6 +59,7 @@
   final String codesignAppstoreId;
   final String codesignTeamId;
   final bool production;
+  final Duration notarizationTimerDuration;
 
   // TODO(xilaizhang): add back utitlity in later splits
   Set<String> fileWithEntitlements = <String>{};
@@ -90,7 +95,6 @@
     </dict>
 </plist>
 ''';
-  static const Duration _notarizationTimerDuration = Duration(seconds: 45);
   static final RegExp _notarytoolStatusCheckPattern = RegExp(r'[ ]*status: ([a-zA-z ]+)');
   static final RegExp _notarytoolRequestPattern = RegExp(r'id: ([a-z0-9-]+)');
 
@@ -119,9 +123,61 @@
   /// The entrance point of examining and code signing an engine artifact.
   Future<void> validateAll() async {
     await Future<void>.value(null);
-    log.info('Codesigned all binaries in ${tempDir.path}');
+    log.info('Codesigned all binaries in ${rootDirectory.path}');
 
-    await tempDir.delete(recursive: true);
+    await rootDirectory.delete(recursive: true);
+  }
+
+  /// Retrieve engine artifact from google cloud storage and kick start a
+  /// recursive visit of its contents.
+  ///
+  /// Invokes [visitDirectory] to recursively visit the contents of the remote
+  /// zip. Also downloads, notarizes and uploads the engine artifact.
+  Future<void> processRemoteZip({
+    required String artifactFilePath,
+    required Directory parentDirectory,
+  }) async {
+    final FileSystem fs = rootDirectory.fileSystem;
+
+    // namespace by hashcode otherwise there will be collisions
+    final String localFilePath = '${artifactFilePath.hashCode}_${fs.path.basename(artifactFilePath)}';
+
+    // download the zip file
+    final File originalFile = await googleCloudStorage.downloadEngineArtifact(
+      from: artifactFilePath,
+      destination: remoteDownloadsDir.childFile(localFilePath).path,
+    );
+
+    await unzip(
+      inputZip: originalFile,
+      outDir: parentDirectory,
+      processManager: processManager,
+    );
+
+    //extract entitlements file.
+    fileWithEntitlements = await parseEntitlements(parentDirectory, true);
+    fileWithoutEntitlements = await parseEntitlements(parentDirectory, false);
+    log.info('parsed binaries with entitlements are $fileWithEntitlements');
+    log.info('parsed binaries without entitlements are $fileWithEntitlements');
+
+    // recursively visit extracted files
+    await visitDirectory(directory: parentDirectory, entitlementParentPath: artifactFilePath);
+
+    final File codesignedFile = codesignedZipsDir.childFile(localFilePath);
+
+    await zip(
+      inputDir: parentDirectory,
+      outputZip: codesignedFile,
+      processManager: processManager,
+    );
+
+    // notarize
+    await notarize(codesignedFile);
+
+    await googleCloudStorage.uploadEngineArtifact(
+      from: codesignedFile.path,
+      destination: artifactFilePath,
+    );
   }
 
   /// Visit a [Directory] type while examining the file system extracted from an artifact.
@@ -144,6 +200,9 @@
         );
         continue;
       }
+      if (entity.basename == 'entitlements.txt' || entity.basename == 'without_entitlements.txt') {
+        continue;
+      }
       final FileType childType = getFileType(
         entity.absolute.path,
         processManager,
@@ -167,7 +226,7 @@
   }) async {
     log.info('This embedded file is ${zipEntity.path} and entitlementParentPath is $entitlementParentPath');
     final String currentFileName = zipEntity.basename;
-    final Directory newDir = tempDir.childDirectory('embedded_zip_${zipEntity.absolute.path.hashCode}');
+    final Directory newDir = rootDirectory.childDirectory('embedded_zip_${zipEntity.absolute.path.hashCode}');
     await unzip(
       inputZip: zipEntity,
       outDir: newDir,
@@ -268,7 +327,7 @@
 
     // check on results
     Timer.periodic(
-      _notarizationTimerDuration,
+      notarizationTimerDuration,
       callback,
     );
     await completer.future;
diff --git a/codesign/lib/src/google_cloud_storage.dart b/codesign/lib/src/google_cloud_storage.dart
new file mode 100644
index 0000000..4b33f5b
--- /dev/null
+++ b/codesign/lib/src/google_cloud_storage.dart
@@ -0,0 +1,55 @@
+// 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:io';
+
+import 'package:file/file.dart';
+import 'package:process/process.dart';
+
+import './utils.dart';
+
+/// A service to interact with google cloud storage through gsutil.
+class GoogleCloudStorage {
+  GoogleCloudStorage({
+    required this.processManager,
+    required this.rootDirectory,
+    required this.commitHash,
+  });
+
+  final ProcessManager processManager;
+  final Directory rootDirectory;
+  final String commitHash;
+  final String gsCloudBaseUrl = 'gs://flutter_infra_release';
+
+  /// Method to upload code signed flutter engine artifact to google cloud bucket.
+  Future<void> uploadEngineArtifact({
+    required String from,
+    required String destination,
+  }) async {
+    final String destinationUrl = '$gsCloudBaseUrl/flutter/$commitHash/$destination';
+
+    final ProcessResult result = await processManager.run(
+      <String>['gsutil', 'cp', from, destinationUrl],
+    );
+
+    if (result.exitCode != 0) {
+      throw CodesignException('Failed to upload $from to $destinationUrl');
+    }
+  }
+
+  /// Method to download flutter engine artifact from google cloud bucket.
+  Future<File> downloadEngineArtifact({
+    required String from,
+    required String destination,
+  }) async {
+    final String sourceUrl = '$gsCloudBaseUrl/flutter/$commitHash/$from';
+    final ProcessResult result = await processManager.run(
+      <String>['gsutil', 'cp', sourceUrl, destination],
+    );
+    if (result.exitCode != 0) {
+      throw CodesignException('Failed to download from $sourceUrl');
+    }
+    return rootDirectory.fileSystem.file(destination);
+  }
+}
diff --git a/codesign/lib/src/utils.dart b/codesign/lib/src/utils.dart
index afe2008..9c946b1 100644
--- a/codesign/lib/src/utils.dart
+++ b/codesign/lib/src/utils.dart
@@ -5,6 +5,7 @@
 import 'dart:io';
 
 import 'package:args/args.dart';
+import 'package:file/file.dart';
 import 'package:process/process.dart';
 
 import 'log.dart';
diff --git a/codesign/test/file_codesign_visitor_test.dart b/codesign/test/file_codesign_visitor_test.dart
index 1b2bdb4..0c03690 100644
--- a/codesign/test/file_codesign_visitor_test.dart
+++ b/codesign/test/file_codesign_visitor_test.dart
@@ -5,28 +5,35 @@
 import 'dart:convert';
 
 import 'package:codesign/codesign.dart' as cs;
+import 'package:codesign/src/google_cloud_storage.dart';
 import 'package:codesign/src/log.dart';
 import 'package:codesign/src/utils.dart';
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
 import 'package:logging/logging.dart';
 import 'package:test/test.dart';
+
 import './src/fake_process_manager.dart';
 
 void main() {
   const String randomString = 'abcd1234';
   final MemoryFileSystem fileSystem = MemoryFileSystem.test();
   const List<String> fakeFilepaths = <String>['a.zip', 'b.zip', 'c.zip'];
+  final Directory rootDirectory = fileSystem.systemTempDirectory.createTempSync('conductor_codesign');
 
   late FakeProcessManager processManager;
-  late Directory tempDir;
+  late GoogleCloudStorage googleCloudStorage;
   late cs.FileCodesignVisitor codesignVisitor;
   final List<LogRecord> records = <LogRecord>[];
 
-  group('visit directory/zip api calls: ', () {
+  group('test google cloud storage and processRemoteZip workflow', () {
     setUp(() {
-      tempDir = fileSystem.systemTempDirectory.createTempSync('conductor_codesign');
       processManager = FakeProcessManager.list(<FakeCommand>[]);
+      googleCloudStorage = GoogleCloudStorage(
+        processManager: processManager,
+        rootDirectory: rootDirectory,
+        commitHash: randomString,
+      );
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
         codesignUserName: randomString,
@@ -35,9 +42,286 @@
         codesignTeamId: randomString,
         codesignFilepaths: fakeFilepaths,
         commitHash: randomString,
+        googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
         processManager: processManager,
-        tempDir: tempDir,
+        rootDirectory: rootDirectory,
+        notarizationTimerDuration: const Duration(seconds: 0),
+      );
+      codesignVisitor.directoriesVisited.clear();
+      records.clear();
+      log.onRecord.listen((LogRecord record) => records.add(record));
+    });
+
+    test('download fails and upload succeeds throws exception', () async {
+      processManager.addCommands(<FakeCommand>[
+        FakeCommand(
+          command: <String>[
+            'gsutil',
+            'cp',
+            randomString,
+            '${googleCloudStorage.gsCloudBaseUrl}/flutter/$randomString/$randomString',
+          ],
+          exitCode: 0,
+        ),
+      ]);
+      expect(
+        () => googleCloudStorage.uploadEngineArtifact(
+          from: randomString,
+          destination: randomString,
+        ),
+        returnsNormally,
+      );
+      processManager.addCommands(<FakeCommand>[
+        FakeCommand(
+          command: <String>[
+            'gsutil',
+            'cp',
+            '${googleCloudStorage.gsCloudBaseUrl}/flutter/$randomString/$randomString',
+            randomString,
+          ],
+          exitCode: -1,
+        ),
+      ]);
+      expect(
+        () => googleCloudStorage.downloadEngineArtifact(
+          from: randomString,
+          destination: randomString,
+        ),
+        throwsA(
+          isA<CodesignException>(),
+        ),
+      );
+    });
+
+    test('download succeeds and upload fails throws exception', () async {
+      processManager.addCommands(<FakeCommand>[
+        FakeCommand(
+          command: <String>[
+            'gsutil',
+            'cp',
+            '${googleCloudStorage.gsCloudBaseUrl}/flutter/$randomString/$randomString',
+            randomString,
+          ],
+          exitCode: 0,
+        ),
+      ]);
+      expect(
+        () => googleCloudStorage.downloadEngineArtifact(
+          from: randomString,
+          destination: randomString,
+        ),
+        returnsNormally,
+      );
+      processManager.addCommands(<FakeCommand>[
+        FakeCommand(
+          command: <String>[
+            'gsutil',
+            'cp',
+            randomString,
+            '${googleCloudStorage.gsCloudBaseUrl}/flutter/$randomString/$randomString',
+          ],
+          exitCode: -1,
+        ),
+      ]);
+      expect(
+        () => googleCloudStorage.uploadEngineArtifact(
+          from: randomString,
+          destination: randomString,
+        ),
+        throwsA(
+          isA<CodesignException>(),
+        ),
+      );
+    });
+
+    test('download succeeds and upload succeeds returns normally', () async {
+      processManager.addCommands(<FakeCommand>[
+        FakeCommand(
+          command: <String>[
+            'gsutil',
+            'cp',
+            '${googleCloudStorage.gsCloudBaseUrl}/flutter/$randomString/$randomString',
+            randomString,
+          ],
+          exitCode: 0,
+        ),
+      ]);
+      expect(
+        () => googleCloudStorage.downloadEngineArtifact(
+          from: randomString,
+          destination: randomString,
+        ),
+        returnsNormally,
+      );
+      processManager.addCommands(<FakeCommand>[
+        FakeCommand(
+          command: <String>[
+            'gsutil',
+            'cp',
+            randomString,
+            '${googleCloudStorage.gsCloudBaseUrl}/flutter/$randomString/$randomString',
+          ],
+          exitCode: 0,
+        ),
+      ]);
+      expect(
+        () => googleCloudStorage.uploadEngineArtifact(
+          from: randomString,
+          destination: randomString,
+        ),
+        returnsNormally,
+      );
+    });
+
+    test('procesRemotezip triggers correct workflow', () async {
+      final String zipFileName = '${rootDirectory.path}/remote_zip_4/folder_1/zip_1';
+      fileSystem.file(zipFileName).createSync(recursive: true);
+      const String artifactFilePath = 'my/artifacts.zip';
+      final String artifactBaseName = rootDirectory.fileSystem.path.basename(artifactFilePath);
+      processManager.addCommands(<FakeCommand>[
+        FakeCommand(
+          command: <String>[
+            'gsutil',
+            'cp',
+            '${googleCloudStorage.gsCloudBaseUrl}/flutter/$randomString/$artifactFilePath',
+            '${rootDirectory.absolute.path}/downloads/${artifactFilePath.hashCode}_$artifactBaseName',
+          ],
+        ),
+        FakeCommand(
+          command: <String>[
+            'unzip',
+            '${rootDirectory.absolute.path}/downloads/${artifactFilePath.hashCode}_$artifactBaseName',
+            '-d',
+            '${rootDirectory.absolute.path}/remote_zip_${artifactFilePath.hashCode}_$artifactBaseName',
+          ],
+          onRun: () => fileSystem
+            ..file('${rootDirectory.path}/remote_zip_${artifactFilePath.hashCode}_$artifactBaseName/entitlements.txt')
+                .createSync(recursive: true)
+            ..file('${rootDirectory.path}/remote_zip_${artifactFilePath.hashCode}_$artifactBaseName/without_entitlements.txt')
+                .createSync(recursive: true),
+        ),
+        FakeCommand(
+          command: <String>[
+            'zip',
+            '--symlinks',
+            '--recurse-paths',
+            '${rootDirectory.absolute.path}/codesigned_zips/${artifactFilePath.hashCode}_$artifactBaseName',
+            '.',
+            '--include',
+            '*',
+          ],
+        ),
+        FakeCommand(
+          command: <String>[
+            'xcrun',
+            'notarytool',
+            'submit',
+            '${rootDirectory.absolute.path}/codesigned_zips/${artifactFilePath.hashCode}_$artifactBaseName',
+            '--apple-id',
+            randomString,
+            '--password',
+            randomString,
+            '--team-id',
+            randomString,
+          ],
+          stdout: 'id: $randomString',
+        ),
+        const FakeCommand(
+          command: <String>[
+            'xcrun',
+            'notarytool',
+            'info',
+            randomString,
+            '--password',
+            randomString,
+            '--apple-id',
+            randomString,
+            '--team-id',
+            randomString,
+          ],
+          stdout: 'status: Accepted',
+        ),
+        FakeCommand(
+          command: <String>[
+            'gsutil',
+            'cp',
+            '${rootDirectory.absolute.path}/codesigned_zips/${artifactFilePath.hashCode}_$artifactBaseName',
+            '${googleCloudStorage.gsCloudBaseUrl}/flutter/$randomString/$artifactFilePath',
+          ],
+        ),
+      ]);
+
+      await codesignVisitor.processRemoteZip(
+        artifactFilePath: artifactFilePath,
+        parentDirectory: rootDirectory.childDirectory('remote_zip_${artifactFilePath.hashCode}_$artifactBaseName'),
+      );
+      final Set<String> messages = records
+          .where((LogRecord record) => record.level == Level.INFO)
+          .map((LogRecord record) => record.message)
+          .toSet();
+      expect(
+        messages,
+        contains(
+            'The downloaded file is unzipped from ${rootDirectory.absolute.path}/downloads/${artifactFilePath.hashCode}_$artifactBaseName to ${rootDirectory.path}/remote_zip_${artifactFilePath.hashCode}_$artifactBaseName'),
+      );
+      expect(
+        messages,
+        contains(
+            'Visiting directory ${rootDirectory.absolute.path}/remote_zip_${artifactFilePath.hashCode}_$artifactBaseName'),
+      );
+      expect(
+        messages,
+        contains('parsed binaries with entitlements are {}'),
+      );
+      expect(
+        messages,
+        contains('parsed binaries without entitlements are {}'),
+      );
+      expect(
+        messages,
+        contains(
+            'uploading xcrun notarytool submit ${rootDirectory.absolute.path}/codesigned_zips/${artifactFilePath.hashCode}_$artifactBaseName --apple-id $randomString --password $randomString --team-id $randomString'),
+      );
+      expect(
+        messages,
+        contains(
+            'RequestUUID for ${rootDirectory.absolute.path}/codesigned_zips/${artifactFilePath.hashCode}_$artifactBaseName is: $randomString'),
+      );
+      expect(
+        messages,
+        contains(
+            'checking notary status with xcrun notarytool info $randomString --password $randomString --apple-id $randomString --team-id $randomString'),
+      );
+      expect(
+        messages,
+        contains(
+            'successfully notarized ${rootDirectory.absolute.path}/codesigned_zips/${artifactFilePath.hashCode}_$artifactBaseName'),
+      );
+    });
+  });
+
+  group('visit directory/zip api calls: ', () {
+    setUp(() {
+      processManager = FakeProcessManager.list(<FakeCommand>[]);
+      googleCloudStorage = GoogleCloudStorage(
+        processManager: processManager,
+        rootDirectory: rootDirectory,
+        commitHash: randomString,
+      );
+      codesignVisitor = cs.FileCodesignVisitor(
+        codesignCertName: randomString,
+        codesignUserName: randomString,
+        appSpecificPassword: randomString,
+        codesignAppstoreId: randomString,
+        codesignTeamId: randomString,
+        codesignFilepaths: fakeFilepaths,
+        commitHash: randomString,
+        googleCloudStorage: googleCloudStorage,
+        fileSystem: fileSystem,
+        processManager: processManager,
+        rootDirectory: rootDirectory,
+        notarizationTimerDuration: const Duration(seconds: 0),
       );
       codesignVisitor.directoriesVisited.clear();
       records.clear();
@@ -46,16 +330,16 @@
 
     test('visitDirectory correctly list files', () async {
       fileSystem
-        ..file('${tempDir.path}/remote_zip_0/file_a').createSync(recursive: true)
-        ..file('${tempDir.path}/remote_zip_0/file_b').createSync(recursive: true)
-        ..file('${tempDir.path}/remote_zip_0/file_c').createSync(recursive: true);
+        ..file('${rootDirectory.path}/remote_zip_0/file_a').createSync(recursive: true)
+        ..file('${rootDirectory.path}/remote_zip_0/file_b').createSync(recursive: true)
+        ..file('${rootDirectory.path}/remote_zip_0/file_c').createSync(recursive: true);
       processManager.addCommands(<FakeCommand>[
         FakeCommand(
           command: <String>[
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/remote_zip_0/file_a',
+            '${rootDirectory.absolute.path}/remote_zip_0/file_a',
           ],
           stdout: 'other_files',
         ),
@@ -64,7 +348,7 @@
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/remote_zip_0/file_b',
+            '${rootDirectory.absolute.path}/remote_zip_0/file_b',
           ],
           stdout: 'other_files',
         ),
@@ -73,12 +357,12 @@
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/remote_zip_0/file_c',
+            '${rootDirectory.absolute.path}/remote_zip_0/file_c',
           ],
           stdout: 'other_files',
         ),
       ]);
-      final Directory testDirectory = fileSystem.directory('${tempDir.path}/remote_zip_0');
+      final Directory testDirectory = fileSystem.directory('${rootDirectory.path}/remote_zip_0');
       await codesignVisitor.visitDirectory(
         directory: testDirectory,
         entitlementParentPath: 'a.zip',
@@ -87,7 +371,7 @@
           .where((LogRecord record) => record.level == Level.INFO)
           .map((LogRecord record) => record.message)
           .toList();
-      expect(messages, contains('Visiting directory ${tempDir.path}/remote_zip_0'));
+      expect(messages, contains('Visiting directory ${rootDirectory.path}/remote_zip_0'));
       expect(messages, contains('Child file of directory remote_zip_0 is file_a'));
       expect(messages, contains('Child file of directory remote_zip_0 is file_b'));
       expect(messages, contains('Child file of directory remote_zip_0 is file_c'));
@@ -95,16 +379,16 @@
 
     test('visitDirectory recursively visits directory', () async {
       fileSystem
-        ..file('${tempDir.path}/remote_zip_1/file_a').createSync(recursive: true)
-        ..file('${tempDir.path}/remote_zip_1/folder_a/file_b').createSync(recursive: true);
-      final Directory testDirectory = fileSystem.directory('${tempDir.path}/remote_zip_1');
+        ..file('${rootDirectory.path}/remote_zip_1/file_a').createSync(recursive: true)
+        ..file('${rootDirectory.path}/remote_zip_1/folder_a/file_b').createSync(recursive: true);
+      final Directory testDirectory = fileSystem.directory('${rootDirectory.path}/remote_zip_1');
       processManager.addCommands(<FakeCommand>[
         FakeCommand(
           command: <String>[
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/remote_zip_1/file_a',
+            '${rootDirectory.absolute.path}/remote_zip_1/file_a',
           ],
           stdout: 'other_files',
         ),
@@ -113,7 +397,7 @@
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/remote_zip_1/folder_a/file_b',
+            '${rootDirectory.absolute.path}/remote_zip_1/folder_a/file_b',
           ],
           stdout: 'other_files',
         ),
@@ -126,32 +410,32 @@
           .where((LogRecord record) => record.level == Level.INFO)
           .map((LogRecord record) => record.message)
           .toList();
-      expect(messages, contains('Visiting directory ${tempDir.path}/remote_zip_1'));
-      expect(messages, contains('Visiting directory ${tempDir.path}/remote_zip_1/folder_a'));
+      expect(messages, contains('Visiting directory ${rootDirectory.path}/remote_zip_1'));
+      expect(messages, contains('Visiting directory ${rootDirectory.path}/remote_zip_1/folder_a'));
       expect(messages, contains('Child file of directory remote_zip_1 is file_a'));
       expect(messages, contains('Child file of directory folder_a is file_b'));
     });
 
     test('visit directory inside a zip', () async {
-      final String zipFileName = '${tempDir.path}/remote_zip_2/zip_1';
+      final String zipFileName = '${rootDirectory.path}/remote_zip_2/zip_1';
       fileSystem.file(zipFileName).createSync(recursive: true);
       processManager.addCommands(<FakeCommand>[
         FakeCommand(
             command: <String>[
               'unzip',
-              '${tempDir.absolute.path}/remote_zip_2/zip_1',
+              '${rootDirectory.absolute.path}/remote_zip_2/zip_1',
               '-d',
-              '${tempDir.absolute.path}/embedded_zip_${zipFileName.hashCode}',
+              '${rootDirectory.absolute.path}/embedded_zip_${zipFileName.hashCode}',
             ],
             onRun: () => fileSystem
-              ..file('${tempDir.path}/embedded_zip_${zipFileName.hashCode}/file_1').createSync(recursive: true)
-              ..file('${tempDir.path}/embedded_zip_${zipFileName.hashCode}/file_2').createSync(recursive: true)),
+              ..file('${rootDirectory.path}/embedded_zip_${zipFileName.hashCode}/file_1').createSync(recursive: true)
+              ..file('${rootDirectory.path}/embedded_zip_${zipFileName.hashCode}/file_2').createSync(recursive: true)),
         FakeCommand(
           command: <String>[
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/embedded_zip_${zipFileName.hashCode}/file_1',
+            '${rootDirectory.absolute.path}/embedded_zip_${zipFileName.hashCode}/file_1',
           ],
           stdout: 'other_files',
         ),
@@ -160,7 +444,7 @@
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/embedded_zip_${zipFileName.hashCode}/file_2',
+            '${rootDirectory.absolute.path}/embedded_zip_${zipFileName.hashCode}/file_2',
           ],
           stdout: 'other_files',
         ),
@@ -169,17 +453,17 @@
             'zip',
             '--symlinks',
             '--recurse-paths',
-            '${tempDir.absolute.path}/remote_zip_2/zip_1',
+            '${rootDirectory.absolute.path}/remote_zip_2/zip_1',
             '.',
             '--include',
             '*'
           ],
-          onRun: () => fileSystem.file('${tempDir.path}/remote_zip_2/zip_1').createSync(recursive: true),
+          onRun: () => fileSystem.file('${rootDirectory.path}/remote_zip_2/zip_1').createSync(recursive: true),
         ),
       ]);
 
       await codesignVisitor.visitEmbeddedZip(
-        zipEntity: fileSystem.file('${tempDir.path}/remote_zip_2/zip_1'),
+        zipEntity: fileSystem.file('${rootDirectory.path}/remote_zip_2/zip_1'),
         entitlementParentPath: 'a.zip',
       );
       final List<String> messages = records
@@ -189,14 +473,14 @@
       expect(
           messages,
           contains(
-              'The downloaded file is unzipped from ${tempDir.path}/remote_zip_2/zip_1 to ${tempDir.path}/embedded_zip_${zipFileName.hashCode}'));
-      expect(messages, contains('Visiting directory ${tempDir.path}/embedded_zip_${zipFileName.hashCode}'));
+              'The downloaded file is unzipped from ${rootDirectory.path}/remote_zip_2/zip_1 to ${rootDirectory.path}/embedded_zip_${zipFileName.hashCode}'));
+      expect(messages, contains('Visiting directory ${rootDirectory.path}/embedded_zip_${zipFileName.hashCode}'));
       expect(messages, contains('Child file of directory embedded_zip_${zipFileName.hashCode} is file_1'));
       expect(messages, contains('Child file of directory embedded_zip_${zipFileName.hashCode} is file_2'));
     });
 
     test('visit zip inside a directory', () async {
-      final String zipFileName = '${tempDir.path}/remote_zip_4/folder_1/zip_1';
+      final String zipFileName = '${rootDirectory.path}/remote_zip_4/folder_1/zip_1';
       fileSystem.file(zipFileName).createSync(recursive: true);
       processManager.addCommands(<FakeCommand>[
         FakeCommand(
@@ -204,25 +488,26 @@
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/remote_zip_4/folder_1/zip_1',
+            '${rootDirectory.absolute.path}/remote_zip_4/folder_1/zip_1',
           ],
           stdout: 'application/zip',
         ),
         FakeCommand(
           command: <String>[
             'unzip',
-            '${tempDir.absolute.path}/remote_zip_4/folder_1/zip_1',
+            '${rootDirectory.absolute.path}/remote_zip_4/folder_1/zip_1',
             '-d',
-            '${tempDir.absolute.path}/embedded_zip_${zipFileName.hashCode}',
+            '${rootDirectory.absolute.path}/embedded_zip_${zipFileName.hashCode}',
           ],
-          onRun: () =>
-              fileSystem.directory('${tempDir.path}/embedded_zip_${zipFileName.hashCode}').createSync(recursive: true),
+          onRun: () => fileSystem
+              .directory('${rootDirectory.path}/embedded_zip_${zipFileName.hashCode}')
+              .createSync(recursive: true),
         ),
         FakeCommand(command: <String>[
           'zip',
           '--symlinks',
           '--recurse-paths',
-          '${tempDir.absolute.path}/remote_zip_4/folder_1/zip_1',
+          '${rootDirectory.absolute.path}/remote_zip_4/folder_1/zip_1',
           '.',
           '--include',
           '*'
@@ -230,31 +515,32 @@
       ]);
 
       await codesignVisitor.visitDirectory(
-        directory: fileSystem.directory('${tempDir.path}/remote_zip_4'),
+        directory: fileSystem.directory('${rootDirectory.path}/remote_zip_4'),
         entitlementParentPath: 'a.zip',
       );
       final List<String> messages = records
           .where((LogRecord record) => record.level == Level.INFO)
           .map((LogRecord record) => record.message)
           .toList();
-      expect(messages, contains('Visiting directory ${tempDir.absolute.path}/remote_zip_4'));
-      expect(messages, contains('Visiting directory ${tempDir.absolute.path}/remote_zip_4/folder_1'));
+      expect(messages, contains('Visiting directory ${rootDirectory.absolute.path}/remote_zip_4'));
+      expect(messages, contains('Visiting directory ${rootDirectory.absolute.path}/remote_zip_4/folder_1'));
       expect(
           messages,
           contains(
-              'The downloaded file is unzipped from ${tempDir.path}/remote_zip_4/folder_1/zip_1 to ${tempDir.path}/embedded_zip_${zipFileName.hashCode}'));
-      expect(messages, contains('Visiting directory ${tempDir.absolute.path}/embedded_zip_${zipFileName.hashCode}'));
+              'The downloaded file is unzipped from ${rootDirectory.path}/remote_zip_4/folder_1/zip_1 to ${rootDirectory.path}/embedded_zip_${zipFileName.hashCode}'));
+      expect(
+          messages, contains('Visiting directory ${rootDirectory.absolute.path}/embedded_zip_${zipFileName.hashCode}'));
     });
 
     test('throw exception when the same directory is visited', () async {
-      fileSystem.file('${tempDir.path}/parent_1/child_1/file_1').createSync(recursive: true);
+      fileSystem.file('${rootDirectory.path}/parent_1/child_1/file_1').createSync(recursive: true);
       processManager.addCommands(<FakeCommand>[
         FakeCommand(
           command: <String>[
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/parent_1/child_1/file_1',
+            '${rootDirectory.absolute.path}/parent_1/child_1/file_1',
           ],
           stdout: 'other_files',
         ),
@@ -263,14 +549,14 @@
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/parent_1/child_1/file_1',
+            '${rootDirectory.absolute.path}/parent_1/child_1/file_1',
           ],
           stdout: 'other_files',
         ),
       ]);
 
       await codesignVisitor.visitDirectory(
-        directory: fileSystem.directory('${tempDir.path}/parent_1/child_1'),
+        directory: fileSystem.directory('${rootDirectory.path}/parent_1/child_1'),
         entitlementParentPath: 'a.zip',
       );
       List<String> warnings = records
@@ -280,7 +566,7 @@
       expect(warnings, isEmpty);
 
       await codesignVisitor.visitDirectory(
-        directory: fileSystem.directory('${tempDir.path}/parent_1'),
+        directory: fileSystem.directory('${rootDirectory.path}/parent_1'),
         entitlementParentPath: 'a.zip',
       );
       warnings = records
@@ -290,23 +576,23 @@
       expect(
           warnings,
           contains(
-              'Warning! You are visiting a directory that has been visited before, the directory is ${tempDir.path}/parent_1/child_1'));
+              'Warning! You are visiting a directory that has been visited before, the directory is ${rootDirectory.path}/parent_1/child_1'));
     });
 
     test('visitBinary codesigns binary with / without entitlement', () async {
       codesignVisitor.fileWithEntitlements = <String>{'root/file_a'};
       codesignVisitor.fileWithoutEntitlements = <String>{'root/file_b'};
       fileSystem
-        ..file('${tempDir.path}/remote_zip_1/file_a').createSync(recursive: true)
-        ..file('${tempDir.path}/remote_zip_1/file_b').createSync(recursive: true);
-      final Directory testDirectory = fileSystem.directory('${tempDir.path}/remote_zip_1');
+        ..file('${rootDirectory.path}/remote_zip_5/file_a').createSync(recursive: true)
+        ..file('${rootDirectory.path}/remote_zip_5/file_b').createSync(recursive: true);
+      final Directory testDirectory = fileSystem.directory('${rootDirectory.path}/remote_zip_5');
       processManager.addCommands(<FakeCommand>[
         FakeCommand(
           command: <String>[
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/remote_zip_1/file_a',
+            '${rootDirectory.absolute.path}/remote_zip_5/file_a',
           ],
           stdout: 'application/x-mach-binary',
         ),
@@ -316,11 +602,11 @@
             '-f',
             '-s',
             randomString,
-            '${tempDir.absolute.path}/remote_zip_1/file_a',
+            '${rootDirectory.absolute.path}/remote_zip_5/file_a',
             '--timestamp',
             '--options=runtime',
             '--entitlements',
-            '${tempDir.absolute.path}/Entitlements.plist'
+            '${rootDirectory.absolute.path}/Entitlements.plist'
           ],
         ),
         FakeCommand(
@@ -328,7 +614,7 @@
             'file',
             '--mime-type',
             '-b',
-            '${tempDir.absolute.path}/remote_zip_1/file_b',
+            '${rootDirectory.absolute.path}/remote_zip_5/file_b',
           ],
           stdout: 'application/x-mach-binary',
         ),
@@ -338,7 +624,7 @@
             '-f',
             '-s',
             randomString,
-            '${tempDir.absolute.path}/remote_zip_1/file_b',
+            '${rootDirectory.absolute.path}/remote_zip_5/file_b',
             '--timestamp',
             '--options=runtime',
           ],
@@ -352,11 +638,11 @@
           .where((LogRecord record) => record.level == Level.INFO)
           .map((LogRecord record) => record.message)
           .toList();
-      expect(messages, contains('signing file at path ${tempDir.absolute.path}/remote_zip_1/file_a'));
+      expect(messages, contains('signing file at path ${rootDirectory.absolute.path}/remote_zip_5/file_a'));
       expect(messages, contains('the virtual entitlement path associated with file is root/file_a'));
       expect(messages, contains('the decision to sign with entitlement is true'));
 
-      expect(messages, contains('signing file at path ${tempDir.absolute.path}/remote_zip_1/file_b'));
+      expect(messages, contains('signing file at path ${rootDirectory.absolute.path}/remote_zip_5/file_b'));
       expect(messages, contains('the virtual entitlement path associated with file is root/file_b'));
       expect(messages, contains('the decision to sign with entitlement is false'));
     });
@@ -364,8 +650,12 @@
 
   group('parse entitlement configs: ', () {
     setUp(() {
-      tempDir = fileSystem.systemTempDirectory.createTempSync('conductor_codesign');
       processManager = FakeProcessManager.list(<FakeCommand>[]);
+      googleCloudStorage = GoogleCloudStorage(
+        processManager: processManager,
+        rootDirectory: rootDirectory,
+        commitHash: randomString,
+      );
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
         codesignUserName: randomString,
@@ -374,9 +664,10 @@
         codesignTeamId: randomString,
         codesignFilepaths: fakeFilepaths,
         commitHash: randomString,
+        googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
         processManager: processManager,
-        tempDir: tempDir,
+        rootDirectory: rootDirectory,
       );
       codesignVisitor.directoriesVisited.clear();
       records.clear();
@@ -384,7 +675,7 @@
     });
 
     test('correctly store file paths', () async {
-      fileSystem.file('${tempDir.absolute.path}/test_entitlement/entitlements.txt')
+      fileSystem.file('${rootDirectory.absolute.path}/test_entitlement/entitlements.txt')
         ..createSync(recursive: true)
         ..writeAsStringSync(
           '''file_a
@@ -394,7 +685,7 @@
           encoding: utf8,
         );
 
-      fileSystem.file('${tempDir.absolute.path}/test_entitlement/without_entitlements.txt')
+      fileSystem.file('${rootDirectory.absolute.path}/test_entitlement/without_entitlements.txt')
         ..createSync(recursive: true)
         ..writeAsStringSync(
           '''file_d
@@ -403,11 +694,11 @@
           encoding: utf8,
         );
       final Set<String> fileWithEntitlements = await codesignVisitor.parseEntitlements(
-        fileSystem.directory('${tempDir.absolute.path}/test_entitlement'),
+        fileSystem.directory('${rootDirectory.absolute.path}/test_entitlement'),
         true,
       );
       final Set<String> fileWithoutEntitlements = await codesignVisitor.parseEntitlements(
-        fileSystem.directory('${tempDir.absolute.path}/test_entitlement'),
+        fileSystem.directory('${rootDirectory.absolute.path}/test_entitlement'),
         false,
       );
       expect(fileWithEntitlements.length, 3);
@@ -428,7 +719,7 @@
     });
 
     test('throw exception when configuration file is missing', () async {
-      fileSystem.file('${tempDir.absolute.path}/test_entitlement/entitlements.txt')
+      fileSystem.file('${rootDirectory.absolute.path}/test_entitlement_2/entitlements.txt')
         ..createSync(recursive: true)
         ..writeAsStringSync(
           '''file_a
@@ -439,7 +730,7 @@
         );
 
       final Set<String> fileWithEntitlements = await codesignVisitor.parseEntitlements(
-        fileSystem.directory('${tempDir.absolute.path}/test_entitlement'),
+        fileSystem.directory('${rootDirectory.absolute.path}/test_entitlement_2'),
         true,
       );
       expect(fileWithEntitlements.length, 3);
@@ -452,7 +743,7 @@
           ]));
       expect(
           () => codesignVisitor.parseEntitlements(
-                fileSystem.directory('/Users/xilaizhang/Desktop/test_entitlement'),
+                fileSystem.directory('/Users/xilaizhang/Desktop/test_entitlement_2'),
                 false,
               ),
           throwsA(
@@ -463,8 +754,12 @@
 
   group('notarization tests: ', () {
     setUp(() {
-      tempDir = fileSystem.systemTempDirectory.createTempSync('conductor_codesign');
       processManager = FakeProcessManager.list(<FakeCommand>[]);
+      googleCloudStorage = GoogleCloudStorage(
+        processManager: processManager,
+        rootDirectory: rootDirectory,
+        commitHash: randomString,
+      );
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
         codesignUserName: randomString,
@@ -473,9 +768,10 @@
         codesignTeamId: randomString,
         codesignFilepaths: fakeFilepaths,
         commitHash: randomString,
+        googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
         processManager: processManager,
-        tempDir: tempDir,
+        rootDirectory: rootDirectory,
       );
       codesignVisitor.directoriesVisited.clear();
       records.clear();
@@ -601,14 +897,14 @@
     });
 
     test('upload notary retries upon failure', () async {
-      fileSystem.file('${tempDir.absolute.path}/temp').createSync();
+      fileSystem.file('${rootDirectory.absolute.path}/temp').createSync();
       processManager.addCommands(<FakeCommand>[
         FakeCommand(
           command: <String>[
             'xcrun',
             'notarytool',
             'submit',
-            '${tempDir.absolute.path}/temp',
+            '${rootDirectory.absolute.path}/temp',
             '--apple-id',
             randomString,
             '--password',
@@ -625,7 +921,7 @@
             'xcrun',
             'notarytool',
             'submit',
-            '${tempDir.absolute.path}/temp',
+            '${rootDirectory.absolute.path}/temp',
             '--apple-id',
             randomString,
             '--password',
@@ -640,7 +936,7 @@
       ]);
 
       final String uuid = codesignVisitor.uploadZipToNotary(
-        fileSystem.file('${tempDir.absolute.path}/temp'),
+        fileSystem.file('${rootDirectory.absolute.path}/temp'),
         3,
         0,
       );
@@ -652,7 +948,7 @@
       expect(
         messages,
         contains('Failed to upload to the notary service with args: '
-            'xcrun notarytool submit ${tempDir.absolute.path}/temp '
+            'xcrun notarytool submit ${rootDirectory.absolute.path}/temp '
             '--apple-id abcd1234 --password abcd1234 --team-id abcd1234'),
       );
       expect(
@@ -662,14 +958,14 @@
     });
 
     test('upload notary throws exception if exit code is unnormal', () async {
-      fileSystem.file('${tempDir.absolute.path}/temp').createSync();
+      fileSystem.file('${rootDirectory.absolute.path}/temp').createSync();
       processManager.addCommands(<FakeCommand>[
         FakeCommand(
           command: <String>[
             'xcrun',
             'notarytool',
             'submit',
-            '${tempDir.absolute.path}/temp',
+            '${rootDirectory.absolute.path}/temp',
             '--apple-id',
             randomString,
             '--password',
@@ -686,7 +982,7 @@
 
       expect(
         () => codesignVisitor.uploadZipToNotary(
-          fileSystem.file('${tempDir.absolute.path}/temp'),
+          fileSystem.file('${rootDirectory.absolute.path}/temp'),
           1,
           0,
         ),
@@ -697,14 +993,14 @@
     });
 
     test('upload notary throws exception after 3 default tries', () async {
-      fileSystem.file('${tempDir.absolute.path}/temp').createSync();
+      fileSystem.file('${rootDirectory.absolute.path}/temp').createSync();
       processManager.addCommands(<FakeCommand>[
         FakeCommand(
           command: <String>[
             'xcrun',
             'notarytool',
             'submit',
-            '${tempDir.absolute.path}/temp',
+            '${rootDirectory.absolute.path}/temp',
             '--apple-id',
             randomString,
             '--password',
@@ -721,7 +1017,7 @@
             'xcrun',
             'notarytool',
             'submit',
-            '${tempDir.absolute.path}/temp',
+            '${rootDirectory.absolute.path}/temp',
             '--apple-id',
             randomString,
             '--password',
@@ -738,7 +1034,7 @@
             'xcrun',
             'notarytool',
             'submit',
-            '${tempDir.absolute.path}/temp',
+            '${rootDirectory.absolute.path}/temp',
             '--apple-id',
             randomString,
             '--password',
@@ -754,7 +1050,7 @@
 
       expect(
         () => codesignVisitor.uploadZipToNotary(
-          fileSystem.file('${tempDir.absolute.path}/temp'),
+          fileSystem.file('${rootDirectory.absolute.path}/temp'),
           3,
           0,
         ),