From ea15fba24ce9901897351263aa5d9bed2bfe7110 Mon Sep 17 00:00:00 2001 From: airherna Date: Fri, 18 Nov 2022 10:41:59 +0100 Subject: [PATCH] List projects from CLI --- .../lib/input/commands/list_command.dart | 21 +++ .../lib/services/project_service.dart | 32 ++-- takeoff/takeoff_cli/lib/takeoff_cli.dart | 2 + takeoff/takeoff_cli/pubspec.lock | 137 +++++++++++++++++- takeoff/takeoff_cli/pubspec.yaml | 6 +- .../test/services/project_service_test.dart | 92 ++++++++++++ .../lib/src/domain/cloud_provider.dart | 16 ++ .../lib/src/domain/cloud_provider_id.dart | 20 ++- .../takeoff_lib/lib/src/takeoff_facade.dart | 1 - takeoff/takeoff_lib/lib/takeoff_lib.dart | 2 + .../test/src/takeoff_facade_test.dart | 1 - 11 files changed, 314 insertions(+), 16 deletions(-) create mode 100644 takeoff/takeoff_cli/lib/input/commands/list_command.dart create mode 100644 takeoff/takeoff_cli/test/services/project_service_test.dart diff --git a/takeoff/takeoff_cli/lib/input/commands/list_command.dart b/takeoff/takeoff_cli/lib/input/commands/list_command.dart new file mode 100644 index 000000000..7223c32e5 --- /dev/null +++ b/takeoff/takeoff_cli/lib/input/commands/list_command.dart @@ -0,0 +1,21 @@ +import 'package:args/command_runner.dart'; +import 'package:takeoff_cli/services/project_service.dart'; + +class ListCommand extends Command { + final ProjectsService service; + @override + final String name = "list"; + @override + final String description = + "List all the projects created from TakeOff with the selected Cloud Provider"; + + ListCommand(this.service) { + argParser.addOption("cloud", + allowed: ["gc", "aws", "azure"], mandatory: true); + } + + @override + void run() { + service.listProjects(argResults?["cloud"]); + } +} diff --git a/takeoff/takeoff_cli/lib/services/project_service.dart b/takeoff/takeoff_cli/lib/services/project_service.dart index 73b6a462c..17dc5cb7e 100644 --- a/takeoff/takeoff_cli/lib/services/project_service.dart +++ b/takeoff/takeoff_cli/lib/services/project_service.dart @@ -8,16 +8,28 @@ class ProjectsService { ); Future initAccount(String cloud, String email) async { - switch (cloud) { - case "gc": - _takeOffFacade.initGoogleCloud(email); - break; - case "aws": - Log.warning("Not implemented yet"); - break; - case "azure": - Log.warning("Not implemented yet"); - break; + CloudProviderId cloudProvider = CloudProviderId.fromString(cloud); + await _takeOffFacade.init(email, cloudProvider); + } + + Future listProjects(String cloud) async { + CloudProviderId providerId = CloudProviderId.fromString(cloud); + CloudProvider provider = CloudProvider.fromId(providerId); + + if ((await _takeOffFacade.getCurrentAccount(providerId)).isEmpty) { + Log.error("You have not logged in with ${provider.name}"); + return; + } + + List projects = await _takeOffFacade.getProjects(providerId); + + if (projects.isEmpty) { + Log.warning("No projects created with ${provider.name}"); + return; + } + print("Projects from ${provider.name}:"); + for (var element in projects) { + print(element); } } } diff --git a/takeoff/takeoff_cli/lib/takeoff_cli.dart b/takeoff/takeoff_cli/lib/takeoff_cli.dart index 1f844f4fc..e5c1253ab 100644 --- a/takeoff/takeoff_cli/lib/takeoff_cli.dart +++ b/takeoff/takeoff_cli/lib/takeoff_cli.dart @@ -1,5 +1,6 @@ import 'package:args/command_runner.dart'; import 'package:takeoff_cli/input/commands/init_command.dart'; +import 'package:takeoff_cli/input/commands/list_command.dart'; import 'package:takeoff_cli/services/project_service.dart'; import 'package:takeoff_lib/takeoff_lib.dart'; @@ -11,6 +12,7 @@ class TakeOffCli { ProjectsService projectsService = ProjectsService(facade); CommandRunner("takeoff", "A CLI to easily create cloud environment.") ..addCommand(InitCommand(projectsService)) + ..addCommand(ListCommand(projectsService)) ..run(args); } } diff --git a/takeoff/takeoff_cli/pubspec.lock b/takeoff/takeoff_cli/pubspec.lock index 7e418f968..8b844a485 100644 --- a/takeoff/takeoff_cli/pubspec.lock +++ b/takeoff/takeoff_cli/pubspec.lock @@ -36,6 +36,76 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.2" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.7" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.4.2" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.0" collection: dependency: transitive description: @@ -64,6 +134,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.2" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.4" file: dependency: transitive description: @@ -71,6 +148,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" frontend_server_client: dependency: transitive description: @@ -79,7 +163,7 @@ packages: source: hosted version: "3.1.0" get_it: - dependency: transitive + dependency: "direct dev" description: name: get_it url: "https://pub.dartlang.org" @@ -92,6 +176,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" http_multi_server: dependency: transitive description: @@ -120,6 +211,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.7.0" lints: dependency: "direct dev" description: @@ -162,6 +260,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + mockito: + dependency: "direct dev" + description: + name: mockito + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.2" node_preamble: dependency: transitive description: @@ -197,8 +302,15 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" - sembast: + pubspec_parse: dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + sembast: + dependency: "direct dev" description: name: sembast url: "https://pub.dartlang.org" @@ -232,6 +344,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.6" source_map_stack_trace: dependency: transitive description: @@ -267,6 +386,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -316,6 +442,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.20" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" typed_data: dependency: transitive description: diff --git a/takeoff/takeoff_cli/pubspec.yaml b/takeoff/takeoff_cli/pubspec.yaml index 6ec3be307..4d621690f 100644 --- a/takeoff/takeoff_cli/pubspec.yaml +++ b/takeoff/takeoff_cli/pubspec.yaml @@ -14,4 +14,8 @@ dependencies: dev_dependencies: lints: ^2.0.0 - test: ^1.16.0 \ No newline at end of file + test: ^1.16.0 + get_it: ^7.2.0 + mockito: ^5.3.2 + build_runner: ^2.3.2 + sembast: ^3.3.1 \ No newline at end of file diff --git a/takeoff/takeoff_cli/test/services/project_service_test.dart b/takeoff/takeoff_cli/test/services/project_service_test.dart new file mode 100644 index 000000000..6575a7f09 --- /dev/null +++ b/takeoff/takeoff_cli/test/services/project_service_test.dart @@ -0,0 +1,92 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:get_it/get_it.dart'; +import 'package:sembast/sembast.dart'; +import 'package:sembast/sembast_io.dart'; +import 'package:takeoff_cli/services/project_service.dart'; +import 'package:takeoff_lib/src/utils/platform/platform_service.dart'; +import 'package:takeoff_lib/src/utils/folders/folders_service.dart'; +import 'package:takeoff_lib/src/persistence/database/database_singleton.dart'; +import 'package:takeoff_lib/src/persistence/cache_repository_impl.dart'; +import 'package:takeoff_lib/src/controllers/persistence/cache_repository.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; +import 'package:test/test.dart'; + +List log = []; + +void main() { + late FoldersService foldersService; + + setUpAll(() { + GetIt.I.registerSingleton(PlatformService()); + foldersService = FoldersService(); + GetIt.I.registerSingleton(foldersService); + }); + + setUp(() async { + log.clear(); + GetIt.I.registerSingleton( + await DatabaseSingleton(dbPath: "project_service_test.db") + .initialize()); + }); + + test( + "listProjects prints the correct message if not logged with Google Cloud", + overridePrint(() async { + ProjectsService projectsService = ProjectsService(TakeOffFacade()); + await projectsService.listProjects("gc"); + + expect(log.length, 1); + expect( + log.first.contains("You have not logged in with Google Cloud"), true); + })); + + test( + "listProjects prints the correct message if no projects are created with Google Cloud", + overridePrint(() async { + CacheRepository cacheRepository = CacheRepositoryImpl(); + String email = "test${Random().nextInt(10000)}@mail.com}"; + await cacheRepository.saveGoogleEmail(email); + ProjectsService projectsService = ProjectsService(TakeOffFacade()); + await projectsService.listProjects("gc"); + + expect(log.length, 1); + expect(log.first.contains("No projects created with Google Cloud"), true); + })); + + test( + "listProjects prints the correct messages if there are projects created with Google Cloud", + overridePrint(() async { + CacheRepository cacheRepository = CacheRepositoryImpl(); + String email = "test${Random().nextInt(10000)}@mail.com}"; + await cacheRepository.saveGoogleEmail(email); + + List projects = + List.generate(15, (_) => Random().nextInt(1000000).toString()); + for (String elem in projects) { + await cacheRepository.saveGoogleProjectId(elem); + } + + ProjectsService projectsService = ProjectsService(TakeOffFacade()); + + await projectsService.listProjects("gc"); + + expect(log.length, 16); + expect(log.first.contains("Projects from Google Cloud:"), true); + expect(log.sublist(1), projects); + })); + + tearDown(() async { + GetIt.I.unregister(); + await databaseFactoryIo.deleteDatabase("project_service_test.db"); + }); +} + +void Function() overridePrint(void Function() testFn) => () { + var spec = ZoneSpecification(print: (_, __, ___, String msg) { + // Add to log instead of printing to stdout + log.add(msg); + }); + return Zone.current.fork(specification: spec).run(testFn); + }; diff --git a/takeoff/takeoff_lib/lib/src/domain/cloud_provider.dart b/takeoff/takeoff_lib/lib/src/domain/cloud_provider.dart index b9d3671ef..7e6085b2e 100644 --- a/takeoff/takeoff_lib/lib/src/domain/cloud_provider.dart +++ b/takeoff/takeoff_lib/lib/src/domain/cloud_provider.dart @@ -1,5 +1,21 @@ +import 'package:takeoff_lib/src/domain/cloud_provider_id.dart'; +import 'package:takeoff_lib/src/domain/gcloud.dart'; + /// Defines all the necessary fields to identify a Cloud Provider abstract class CloudProvider { + CloudProvider(); + String get hostFolderName; String get name; + + factory CloudProvider.fromId(CloudProviderId id) { + switch (id) { + case CloudProviderId.gcloud: + return GCloud(); + case CloudProviderId.aws: + throw UnsupportedError("Cloud provider AWS currently not supported"); + case CloudProviderId.azure: + throw UnsupportedError("Cloud provider Azure currently not supported"); + } + } } diff --git a/takeoff/takeoff_lib/lib/src/domain/cloud_provider_id.dart b/takeoff/takeoff_lib/lib/src/domain/cloud_provider_id.dart index f9030934d..64595ee64 100644 --- a/takeoff/takeoff_lib/lib/src/domain/cloud_provider_id.dart +++ b/takeoff/takeoff_lib/lib/src/domain/cloud_provider_id.dart @@ -1,2 +1,20 @@ /// Enum to distinguish between cloud providers -enum CloudProviderId { gcloud, aws, azure } +enum CloudProviderId { + gcloud, + aws, + azure; + + factory CloudProviderId.fromString(String string) { + switch (string) { + case "gc": + return gcloud; + case "aws": + return aws; + case "azure": + return azure; + default: + throw UnsupportedError( + 'Values for cloud provider can be "gc", "aws" or "azure"'); + } + } +} diff --git a/takeoff/takeoff_lib/lib/src/takeoff_facade.dart b/takeoff/takeoff_lib/lib/src/takeoff_facade.dart index 5d10eb090..01f6d8011 100644 --- a/takeoff/takeoff_lib/lib/src/takeoff_facade.dart +++ b/takeoff/takeoff_lib/lib/src/takeoff_facade.dart @@ -6,7 +6,6 @@ import 'package:takeoff_lib/src/controllers/cloud_providers/gcloud_controller.da import 'package:takeoff_lib/src/controllers/docker/docker_controller.dart'; import 'package:takeoff_lib/src/controllers/docker/docker_controller_factory.dart'; import 'package:takeoff_lib/src/controllers/persistence/cache_repository.dart'; -import 'package:takeoff_lib/src/domain/cloud_provider.dart'; import 'package:takeoff_lib/src/domain/cloud_provider_id.dart'; import 'package:takeoff_lib/src/hangar_scripts/common/pipeline_generator/language.dart'; import 'package:takeoff_lib/src/persistence/cache_repository_impl.dart'; diff --git a/takeoff/takeoff_lib/lib/takeoff_lib.dart b/takeoff/takeoff_lib/lib/takeoff_lib.dart index 0749c1c93..bd3aeaaf4 100644 --- a/takeoff/takeoff_lib/lib/takeoff_lib.dart +++ b/takeoff/takeoff_lib/lib/takeoff_lib.dart @@ -5,5 +5,7 @@ library takeoff_lib; export 'src/takeoff_facade.dart'; export 'src/utils/logger/log.dart'; +export 'src/domain/cloud_provider.dart'; +export 'src/domain/cloud_provider_id.dart'; // TODO: Export any libraries intended for clients of this package. diff --git a/takeoff/takeoff_lib/test/src/takeoff_facade_test.dart b/takeoff/takeoff_lib/test/src/takeoff_facade_test.dart index 0426bc94f..eed27cdc1 100644 --- a/takeoff/takeoff_lib/test/src/takeoff_facade_test.dart +++ b/takeoff/takeoff_lib/test/src/takeoff_facade_test.dart @@ -5,7 +5,6 @@ import 'package:get_it/get_it.dart'; import 'package:path/path.dart'; import 'package:sembast/sembast.dart'; import 'package:sembast/sembast_io.dart'; -import 'package:takeoff_lib/src/controllers/cloud_providers/gcloud_controller.dart'; import 'package:takeoff_lib/src/controllers/persistence/cache_repository.dart'; import 'package:takeoff_lib/src/domain/cloud_provider_id.dart'; import 'package:takeoff_lib/src/persistence/cache_repository_impl.dart';