Conversation
Codecov Report
@@ Coverage Diff @@ ## develop #1160 +/- ## =========================================== + Coverage 28.67% 28.81% +0.13% =========================================== Files 412 414 +2 Lines 12755 12760 +5 =========================================== + Hits 3658 3677 +19 + Misses 9097 9083 -14
Continue to review full report at Codecov.
|
Since Database.reset_db() calls ConfigService.init_config() which calls ConfigService.reset_config() which calls services.post_breach_files.remove_PBA_files(), it is redundant to call remove_PBA_files() from Database.reset_db(). Removing this call has the added benefit of reducing the coupling between the Database service and services.post_breach_files
7cb9bcf to 9476441 Compare | patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:25) | ||
| patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:31) | ||
| mock_home_env # unused variable (monkey/tests/monkey_island/cc/server_utils/test_island_logger.py:20) | ||
| custom_pba_directory # unused variable (monkey/tests/monkey_island/cc/services/test_post_breach_files.py:20) |
There was a problem hiding this comment.
Any reason for adding this here exactly? If not, maybe we should make it a rule to add these at the end of the file along with the PR/commit it was introduced in so it's easy to track, if ever needed.
There was a problem hiding this comment.
I put it here because it's among the other monkey/tests/monkey_island files. But we can just make a rule of adding whitelist lines at the end in the future.
There was a problem hiding this comment.
I don't understand this. I don't see any custom_pba_directory in monkey/tests/monkey_island/cc/services/test_post_breach_files.py on line 20. I presume it's talking about the fixture, right?
There was a problem hiding this comment.
Yes, a lot of the line numbers in this file probably don't match. Maybe we should remove them, the filename should be enough.
| | ||
| | ||
| def run_local_monkey(): | ||
| def run_local_monkey(dest_dir): |
There was a problem hiding this comment.
This whole method should end up in a service. Not sure if this is "in scope" of this PR though
| file_path = ( | ||
| Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename).absolute() | ||
| Path(PostBreachFilesService.get_custom_pba_directory()).joinpath(filename).absolute() | ||
| ) |
There was a problem hiding this comment.
Why do we need these parentheses?
There was a problem hiding this comment.
The line is longer that 100 characters, so this is how black formatted it.
| windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) | ||
| | ||
| config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename | ||
| config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename |
There was a problem hiding this comment.
Should be added to common.config_value_paths and used from there?
| create_custom_pba_file("linux_file") | ||
| create_custom_pba_file("windows_file") | ||
| | ||
| custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) |
There was a problem hiding this comment.
typo: custom_pda_dir_contents -> custom_pba_dir_contents
| create_custom_pba_file("windows_file") | ||
| | ||
| custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) | ||
| assert len(custom_pda_dir_contents) == 2 |
There was a problem hiding this comment.
This tests create_custom_pba_file which is defined in the unit test. Maybe it would be better to do assertions of file created in the create_custom_pba_file method itself? Not a strict requirement though, you can leave as is.
There was a problem hiding this comment.
Yeah, that assert is there to make sure the test doesn't fail silently. From the test's perspective, it says:
- make sure some files are in the dir
- call
remove_PBA_files() - make sure the dir is emtpy
There may be a better implementation, but I don't think that moving the assert to create_custom_pba_file() is it. I think I can add some utility methods to make the intent clearer.
monkey/test.py Outdated
| @@ -0,0 +1,4 @@ | |||
| try: | |||
VakarisZ left a comment
There was a problem hiding this comment.
Some improvements worth considering
DeepCode's analysis on #b8d445 found:
Top issues
👉 View analysis in DeepCode’s Dashboard | Configure the bot |
| @@ -0,0 +1,4 @@ | |||
| #!/bin/bash | |||
| logger = logging.getLogger(__name__) | ||
| | ||
| | ||
| class RunLocalMonkeyService: |
There was a problem hiding this comment.
Should be a noun like LocalMonkeyRunService, now it sounds like it's a method that runs the service.
| from monkey_island.cc.services.run_local_monkey import RunLocalMonkeyService | ||
| | ||
| | ||
| def initialize_services(data_dir): |
There was a problem hiding this comment.
If we need to add more configurable values we'll just keep adding parameters to this method like def initialize_services(data_dir, island_port, bootloader_port):? . PostBreachFilesService and RunLocalMonkeyService doesn't need any special initialization, they just need to access configuration parameters.
There was a problem hiding this comment.
If we need to add more configurable values we'll just keep adding parameters to this method like def initialize_services(data_dir, island_port, bootloader_port):?
Yes. If the configuration list gets too long, we can create a config object and pass in the config object, rather than individual parameters. The initialization function can then pass only those things that each service needs to that service.
PostBreachFilesService and RunLocalMonkeyService doesn't need any special initialization, they just need to access configuration parameters.
Right, but they only need access to one specific configuration parameter. By passing them exactly what the need, they become decoupled from global state, easier to test, and their dependencies are clearer.
If we decide that either service should write its data somewhere else, we simply pass it a different directory. No code changes to the services are necessary. If they rely on a globally accessible config singleton, we need to modify the services themselves. Giving each service exactly what it needs to get its job done keeps it decoupled and flexible.
At the moment, we're hamstrung by the architecture, which is a bunch of classes that pretend to be static and contain no state, but actually do contain state in the form of an environment singleton. These services should be instance objects that are passed to the resources via dependency injection or accessed via a service locator.
There was a problem hiding this comment.
Yes. If the configuration list gets too long, we can create a config object and pass in the config object, rather than individual parameters. The initialization function can then pass only those things that each service needs to that service.
So what I'm suggesting is decoupling environment config files from the singleton. And that singleton would be the config object.
Right, but they only need access to one specific configuration parameter. By passing them exactly what the need, they become decoupled from global state, easier to test, and their dependencies are clearer.
So why not do:
class PublicVarReader(): PUBLIC_VAR = env_singleton.public_var This also passes exactly one configuration parameter and when testing you'd mock PUBLIC_VAR just the same. If public_var is indeed a global config parameter, like island port, it should stay in environment singleton no matter what. What could change is that one day we decide to pull the island port from cmd instead of a file, but that's the responsibility of a singleton builder. Imagine we added the ability to customize the port of an island by specifying it in config file. If you want to create a class that needs to know the island port, you'd first need to create a "class builder" somewhere else in the codebase to create a class itself. That's less intuitive than reading a global value from a singleton and adds unnecessary complexity IMO.
Nevertheless, if you thought about it and made this design choice with care, I trust that you have a good long term vision for the design and don't require a change.
There was a problem hiding this comment.
I trust that you have a good long term vision for the design and don't require a change.
I have a long term plan :)
So what I'm suggesting is decoupling environment config files from the singleton.
Ultimately, environment config will very likely go away. The deployment type shouldn't really be configurable, at least not in the sense that the log level is configurable. The user password and hash shouldn't be written into a config file, but some other file specifically for tracking that that is read-only. We should, at some point, replace our authentication system with something 3rd party (it would be more secure) that would support multiple users (our users have asked for multi-user support).
This also passes exactly one configuration parameter and when testing you'd mock PUBLIC_VAR just the same.
Making this global means your class is coupled to the specific details of how configuration is managed. It also means you need to mock it; in other words, your tests are also coupled to the specific details of your configuration. Passing a class the values it needs decreases coupling, simplifies testing, and makes the interface explicit. Things that are decoupled are easier to change (see The Pragmatic Programmer, The Essence of Good Design and Decoupling).
Anyway, we can discuss this more on a call (better communication faster).
VakarisZ left a comment
There was a problem hiding this comment.
Some minor things, but the biggest question that I spent a lot of time thinking about is if this we need to initialize **classes** whenever we want to read a global parameter is a good design choice.
c8cc968 to 79eb744 Compare
It's not a good design choice. We should be using dependency injection or the service locator pattern. Hence this TODO: Resolving this ATM is outside the scope of our current goal, which is to write all the data to a configurable, writable location on all platforms. This solution is a hack that is reasonably easy to fix and adapt to whatever architectural design decisions we make regarding how resources access services. |

What does this PR do?
Issue #1146
Refactor usage of
data_dir:Decouples the environment config from components that require the
data_dirAdds tests
Removes circular dependency between ConfigService and PostBreachFilesService
Consolidates some duplicate functionality
PR Checklist
Was the CHANGELOG.md updated to reflect the changes?Was the documentation framework updated to reflect the changes?Testing Checklist
If applicable, add screenshots or log transcripts of the feature working