runbooks 0.2.5__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. conftest.py +26 -0
  2. jupyter-agent/.env +2 -0
  3. jupyter-agent/.env.template +2 -0
  4. jupyter-agent/.gitattributes +35 -0
  5. jupyter-agent/.gradio/certificate.pem +31 -0
  6. jupyter-agent/README.md +16 -0
  7. jupyter-agent/__main__.log +8 -0
  8. jupyter-agent/app.py +256 -0
  9. jupyter-agent/cloudops-agent.png +0 -0
  10. jupyter-agent/ds-system-prompt.txt +154 -0
  11. jupyter-agent/jupyter-agent.png +0 -0
  12. jupyter-agent/llama3_template.jinja +123 -0
  13. jupyter-agent/requirements.txt +9 -0
  14. jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +68 -0
  15. jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +91 -0
  16. jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +91 -0
  17. jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +57 -0
  18. jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +53 -0
  19. jupyter-agent/tmp/jupyter-agent.ipynb +27 -0
  20. jupyter-agent/utils.py +409 -0
  21. runbooks/__init__.py +71 -3
  22. runbooks/__main__.py +13 -0
  23. runbooks/aws/ec2_describe_instances.py +1 -1
  24. runbooks/aws/ec2_run_instances.py +8 -2
  25. runbooks/aws/ec2_start_stop_instances.py +17 -4
  26. runbooks/aws/ec2_unused_volumes.py +5 -1
  27. runbooks/aws/s3_create_bucket.py +4 -2
  28. runbooks/aws/s3_list_objects.py +6 -1
  29. runbooks/aws/tagging_lambda_handler.py +13 -2
  30. runbooks/aws/tags.json +12 -0
  31. runbooks/base.py +353 -0
  32. runbooks/cfat/README.md +49 -0
  33. runbooks/cfat/__init__.py +74 -0
  34. runbooks/cfat/app.ts +644 -0
  35. runbooks/cfat/assessment/__init__.py +40 -0
  36. runbooks/cfat/assessment/asana-import.csv +39 -0
  37. runbooks/cfat/assessment/cfat-checks.csv +31 -0
  38. runbooks/cfat/assessment/cfat.txt +520 -0
  39. runbooks/cfat/assessment/collectors.py +200 -0
  40. runbooks/cfat/assessment/jira-import.csv +39 -0
  41. runbooks/cfat/assessment/runner.py +387 -0
  42. runbooks/cfat/assessment/validators.py +290 -0
  43. runbooks/cfat/cli.py +103 -0
  44. runbooks/cfat/docs/asana-import.csv +24 -0
  45. runbooks/cfat/docs/cfat-checks.csv +31 -0
  46. runbooks/cfat/docs/cfat.txt +335 -0
  47. runbooks/cfat/docs/checks-output.png +0 -0
  48. runbooks/cfat/docs/cloudshell-console-run.png +0 -0
  49. runbooks/cfat/docs/cloudshell-download.png +0 -0
  50. runbooks/cfat/docs/cloudshell-output.png +0 -0
  51. runbooks/cfat/docs/downloadfile.png +0 -0
  52. runbooks/cfat/docs/jira-import.csv +24 -0
  53. runbooks/cfat/docs/open-cloudshell.png +0 -0
  54. runbooks/cfat/docs/report-header.png +0 -0
  55. runbooks/cfat/models.py +1026 -0
  56. runbooks/cfat/package-lock.json +5116 -0
  57. runbooks/cfat/package.json +38 -0
  58. runbooks/cfat/report.py +496 -0
  59. runbooks/cfat/reporting/__init__.py +46 -0
  60. runbooks/cfat/reporting/exporters.py +337 -0
  61. runbooks/cfat/reporting/formatters.py +496 -0
  62. runbooks/cfat/reporting/templates.py +135 -0
  63. runbooks/cfat/run-assessment.sh +23 -0
  64. runbooks/cfat/runner.py +69 -0
  65. runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
  66. runbooks/cfat/src/actions/check-config-existence.ts +37 -0
  67. runbooks/cfat/src/actions/check-control-tower.ts +37 -0
  68. runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
  69. runbooks/cfat/src/actions/check-iam-users.ts +50 -0
  70. runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
  71. runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
  72. runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
  73. runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
  74. runbooks/cfat/src/actions/create-backlog.ts +372 -0
  75. runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
  76. runbooks/cfat/src/actions/create-report.ts +616 -0
  77. runbooks/cfat/src/actions/define-account-type.ts +51 -0
  78. runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
  79. runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
  80. runbooks/cfat/src/actions/get-idc-info.ts +34 -0
  81. runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
  82. runbooks/cfat/src/actions/get-org-details.ts +35 -0
  83. runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
  84. runbooks/cfat/src/actions/get-org-ous.ts +35 -0
  85. runbooks/cfat/src/actions/get-regions.ts +22 -0
  86. runbooks/cfat/src/actions/zip-assessment.ts +27 -0
  87. runbooks/cfat/src/types/index.d.ts +147 -0
  88. runbooks/cfat/tests/__init__.py +141 -0
  89. runbooks/cfat/tests/test_cli.py +340 -0
  90. runbooks/cfat/tests/test_integration.py +290 -0
  91. runbooks/cfat/tests/test_models.py +505 -0
  92. runbooks/cfat/tests/test_reporting.py +354 -0
  93. runbooks/cfat/tsconfig.json +16 -0
  94. runbooks/cfat/webpack.config.cjs +27 -0
  95. runbooks/config.py +260 -0
  96. runbooks/finops/README.md +337 -0
  97. runbooks/finops/__init__.py +86 -0
  98. runbooks/finops/aws_client.py +245 -0
  99. runbooks/finops/cli.py +151 -0
  100. runbooks/finops/cost_processor.py +410 -0
  101. runbooks/finops/dashboard_runner.py +448 -0
  102. runbooks/finops/helpers.py +355 -0
  103. runbooks/finops/main.py +14 -0
  104. runbooks/finops/profile_processor.py +174 -0
  105. runbooks/finops/types.py +66 -0
  106. runbooks/finops/visualisations.py +80 -0
  107. runbooks/inventory/.gitignore +354 -0
  108. runbooks/inventory/ArgumentsClass.py +261 -0
  109. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +619 -0
  110. runbooks/inventory/Inventory_Modules.py +6130 -0
  111. runbooks/inventory/LandingZone/delete_lz.py +1075 -0
  112. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +738 -0
  113. runbooks/inventory/README.md +1320 -0
  114. runbooks/inventory/__init__.py +62 -0
  115. runbooks/inventory/account_class.py +532 -0
  116. runbooks/inventory/all_my_instances_wrapper.py +123 -0
  117. runbooks/inventory/aws_decorators.py +201 -0
  118. runbooks/inventory/aws_organization.png +0 -0
  119. runbooks/inventory/cfn_move_stack_instances.py +1526 -0
  120. runbooks/inventory/check_cloudtrail_compliance.py +614 -0
  121. runbooks/inventory/check_controltower_readiness.py +1107 -0
  122. runbooks/inventory/check_landingzone_readiness.py +711 -0
  123. runbooks/inventory/cloudtrail.md +727 -0
  124. runbooks/inventory/collectors/__init__.py +20 -0
  125. runbooks/inventory/collectors/aws_compute.py +518 -0
  126. runbooks/inventory/collectors/aws_networking.py +275 -0
  127. runbooks/inventory/collectors/base.py +222 -0
  128. runbooks/inventory/core/__init__.py +19 -0
  129. runbooks/inventory/core/collector.py +303 -0
  130. runbooks/inventory/core/formatter.py +296 -0
  131. runbooks/inventory/delete_s3_buckets_objects.py +169 -0
  132. runbooks/inventory/discovery.md +81 -0
  133. runbooks/inventory/draw_org_structure.py +748 -0
  134. runbooks/inventory/ec2_vpc_utils.py +341 -0
  135. runbooks/inventory/find_cfn_drift_detection.py +272 -0
  136. runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
  137. runbooks/inventory/find_cfn_stackset_drift.py +733 -0
  138. runbooks/inventory/find_ec2_security_groups.py +669 -0
  139. runbooks/inventory/find_landingzone_versions.py +201 -0
  140. runbooks/inventory/find_vpc_flow_logs.py +1221 -0
  141. runbooks/inventory/inventory.sh +659 -0
  142. runbooks/inventory/list_cfn_stacks.py +558 -0
  143. runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
  144. runbooks/inventory/list_cfn_stackset_operations.py +734 -0
  145. runbooks/inventory/list_cfn_stacksets.py +453 -0
  146. runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
  147. runbooks/inventory/list_ds_directories.py +354 -0
  148. runbooks/inventory/list_ec2_availability_zones.py +286 -0
  149. runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
  150. runbooks/inventory/list_ec2_instances.py +425 -0
  151. runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
  152. runbooks/inventory/list_elbs_load_balancers.py +411 -0
  153. runbooks/inventory/list_enis_network_interfaces.py +526 -0
  154. runbooks/inventory/list_guardduty_detectors.py +568 -0
  155. runbooks/inventory/list_iam_policies.py +404 -0
  156. runbooks/inventory/list_iam_roles.py +518 -0
  157. runbooks/inventory/list_iam_saml_providers.py +359 -0
  158. runbooks/inventory/list_lambda_functions.py +882 -0
  159. runbooks/inventory/list_org_accounts.py +446 -0
  160. runbooks/inventory/list_org_accounts_users.py +354 -0
  161. runbooks/inventory/list_rds_db_instances.py +406 -0
  162. runbooks/inventory/list_route53_hosted_zones.py +318 -0
  163. runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
  164. runbooks/inventory/list_sns_topics.py +360 -0
  165. runbooks/inventory/list_ssm_parameters.py +402 -0
  166. runbooks/inventory/list_vpc_subnets.py +433 -0
  167. runbooks/inventory/list_vpcs.py +422 -0
  168. runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
  169. runbooks/inventory/models/__init__.py +24 -0
  170. runbooks/inventory/models/account.py +192 -0
  171. runbooks/inventory/models/inventory.py +309 -0
  172. runbooks/inventory/models/resource.py +247 -0
  173. runbooks/inventory/recover_cfn_stack_ids.py +205 -0
  174. runbooks/inventory/requirements.txt +12 -0
  175. runbooks/inventory/run_on_multi_accounts.py +211 -0
  176. runbooks/inventory/tests/common_test_data.py +3661 -0
  177. runbooks/inventory/tests/common_test_functions.py +204 -0
  178. runbooks/inventory/tests/setup.py +24 -0
  179. runbooks/inventory/tests/src.py +18 -0
  180. runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
  181. runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
  182. runbooks/inventory/tests/test_inventory_modules.py +55 -0
  183. runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
  184. runbooks/inventory/tests/test_moto_integration_example.py +273 -0
  185. runbooks/inventory/tests/test_org_list_accounts.py +49 -0
  186. runbooks/inventory/update_aws_actions.py +173 -0
  187. runbooks/inventory/update_cfn_stacksets.py +1215 -0
  188. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
  189. runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
  190. runbooks/inventory/update_s3_public_access_block.py +539 -0
  191. runbooks/inventory/utils/__init__.py +23 -0
  192. runbooks/inventory/utils/aws_helpers.py +510 -0
  193. runbooks/inventory/utils/threading_utils.py +493 -0
  194. runbooks/inventory/utils/validation.py +682 -0
  195. runbooks/inventory/verify_ec2_security_groups.py +1430 -0
  196. runbooks/main.py +1004 -0
  197. runbooks/organizations/__init__.py +12 -0
  198. runbooks/organizations/manager.py +374 -0
  199. runbooks/security/README.md +447 -0
  200. runbooks/security/__init__.py +71 -0
  201. runbooks/{security_baseline → security}/checklist/alternate_contacts.py +8 -1
  202. runbooks/{security_baseline → security}/checklist/bucket_public_access.py +4 -1
  203. runbooks/{security_baseline → security}/checklist/cloudwatch_alarm_configuration.py +9 -2
  204. runbooks/{security_baseline → security}/checklist/guardduty_enabled.py +9 -2
  205. runbooks/{security_baseline → security}/checklist/multi_region_instance_usage.py +5 -1
  206. runbooks/{security_baseline → security}/checklist/root_access_key.py +6 -1
  207. runbooks/{security_baseline → security}/config-origin.json +1 -1
  208. runbooks/{security_baseline → security}/config.json +1 -1
  209. runbooks/{security_baseline → security}/permission.json +1 -1
  210. runbooks/{security_baseline → security}/report_generator.py +10 -2
  211. runbooks/{security_baseline → security}/report_template_en.html +7 -7
  212. runbooks/{security_baseline → security}/report_template_jp.html +7 -7
  213. runbooks/{security_baseline → security}/report_template_kr.html +12 -12
  214. runbooks/{security_baseline → security}/report_template_vn.html +7 -7
  215. runbooks/{security_baseline → security}/run_script.py +8 -2
  216. runbooks/{security_baseline → security}/security_baseline_tester.py +12 -4
  217. runbooks/{security_baseline → security}/utils/common.py +5 -1
  218. runbooks/utils/__init__.py +204 -0
  219. runbooks-0.7.0.dist-info/METADATA +375 -0
  220. runbooks-0.7.0.dist-info/RECORD +249 -0
  221. {runbooks-0.2.5.dist-info → runbooks-0.7.0.dist-info}/WHEEL +1 -1
  222. runbooks-0.7.0.dist-info/entry_points.txt +7 -0
  223. runbooks-0.7.0.dist-info/licenses/LICENSE +201 -0
  224. runbooks-0.7.0.dist-info/top_level.txt +3 -0
  225. runbooks/python101/calculator.py +0 -34
  226. runbooks/python101/config.py +0 -1
  227. runbooks/python101/exceptions.py +0 -16
  228. runbooks/python101/file_manager.py +0 -218
  229. runbooks/python101/toolkit.py +0 -153
  230. runbooks-0.2.5.dist-info/METADATA +0 -439
  231. runbooks-0.2.5.dist-info/RECORD +0 -61
  232. runbooks-0.2.5.dist-info/entry_points.txt +0 -3
  233. runbooks-0.2.5.dist-info/top_level.txt +0 -1
  234. /runbooks/{security_baseline/__init__.py → inventory/tests/script_test_data.py} +0 -0
  235. /runbooks/{security_baseline → security}/checklist/__init__.py +0 -0
  236. /runbooks/{security_baseline → security}/checklist/account_level_bucket_public_access.py +0 -0
  237. /runbooks/{security_baseline → security}/checklist/direct_attached_policy.py +0 -0
  238. /runbooks/{security_baseline → security}/checklist/iam_password_policy.py +0 -0
  239. /runbooks/{security_baseline → security}/checklist/iam_user_mfa.py +0 -0
  240. /runbooks/{security_baseline → security}/checklist/multi_region_trail.py +0 -0
  241. /runbooks/{security_baseline → security}/checklist/root_mfa.py +0 -0
  242. /runbooks/{security_baseline → security}/checklist/root_usage.py +0 -0
  243. /runbooks/{security_baseline → security}/checklist/trail_enabled.py +0 -0
  244. /runbooks/{security_baseline → security}/checklist/trusted_advisor.py +0 -0
  245. /runbooks/{security_baseline → security}/utils/__init__.py +0 -0
  246. /runbooks/{security_baseline → security}/utils/enums.py +0 -0
  247. /runbooks/{security_baseline → security}/utils/language.py +0 -0
  248. /runbooks/{security_baseline → security}/utils/level_const.py +0 -0
  249. /runbooks/{security_baseline → security}/utils/permission_list.py +0 -0
@@ -0,0 +1,86 @@
1
+ import pytest
2
+ from common_test_data import CredentialResponseData, get_all_my_functions_test_result_dict
3
+ from common_test_functions import _amend_create_boto3_session, _amend_make_api_call, _amend_make_api_call_specific
4
+ from lambda_list_functions import collect_all_my_functions, fix_my_functions
5
+
6
+ # CredentialList = test_get_all_credentials()
7
+ # This is what the response from the tested function should look like, given the parameters supplied
8
+
9
+ """
10
+ Pass in the parameters as dictionary with operation names and what test results I should get back
11
+ """
12
+ function_parameters = {"CredentialList": CredentialResponseData, "pFragments": ["python3.9", "Metric"], "pverbose": 20}
13
+
14
+
15
+ @pytest.mark.parametrize(
16
+ "parameters, test_value_dict",
17
+ [
18
+ (function_parameters, get_all_my_functions_test_result_dict),
19
+ # (RootOnlyParams, ),
20
+ # str(1993),
21
+ # json.dumps({"SecretString": "my-secret"}),
22
+ # json.dumps([2, 3, 5, 7, 11, 13, 17, 19]),
23
+ # KeyError("How dare you touch my secret!"),
24
+ # ValueError("Oh my goodness you even have the guts to repeat it!!!"),
25
+ ],
26
+ )
27
+ def test_all_my_functions(parameters, test_value_dict, mocker):
28
+ """
29
+ Description:
30
+ @param parameters: the expected parameters into the function
31
+ @param mocker: the mock object
32
+ @return:
33
+ """
34
+ CredentialList = parameters["CredentialList"]
35
+ pFragments = parameters["pFragments"]
36
+ verbose = parameters["pverbose"]
37
+ test_data = {"FunctionName": "all_my_functions", "AccountSpecific": True, "RegionSpecific": True}
38
+ # _amend_create_boto3_session(test_data, mocker)
39
+ # pass
40
+ # _amend_make_api_call(test_data, test_value_dict, mocker)
41
+ _amend_make_api_call_specific(test_data, test_value_dict, mocker)
42
+
43
+ # if isinstance(test_value, Exception):
44
+ # print("Expected Error...")
45
+ # with pytest.raises(type(test_value)) as error:
46
+ # all_my_functions(CredentialList, pFragments, verbose)
47
+ # result = error
48
+ # else:
49
+ result = collect_all_my_functions(CredentialList, pFragments, verbose)
50
+
51
+ print("Result:", result)
52
+ print()
53
+
54
+
55
+ """
56
+ import boto3
57
+ from unittest.mock import patch, MagicMock
58
+
59
+
60
+ # Your application code
61
+ def get_lambda_functions():
62
+ session = boto3.Session('LZ1', region_name='us-east-2')
63
+ lambda_client = session.client('lambda')
64
+ return lambda_client.list_functions()
65
+
66
+
67
+ # Your mock method
68
+ def mock_list_functions(*args, **kwargs):
69
+ print("Inside the mocked function!")
70
+ # Access the session info here (if it's passed or globally accessible)
71
+ print()
72
+ creds = args[1]['test'].get_credentials()
73
+ return {'Functions': [{'FunctionName': 'mocked_function_1'}, {'FunctionName': 'mocked_function_2'}]}
74
+
75
+
76
+ # Your test code
77
+ def test_get_lambda_functions():
78
+ with patch.object(boto3.Session, 'client') as mock_client:
79
+ mock_lambda_client = MagicMock()
80
+ addl_params = {'test': boto3.Session(), 'account': '11111'}
81
+ mock_lambda_client.list_functions = mock_list_functions('lambda', addl_params)
82
+ mock_client.return_value = mock_lambda_client
83
+
84
+ result = get_lambda_functions()
85
+ assert result == {'Functions': [{'FunctionName': 'mocked_function_1'}, {'FunctionName': 'mocked_function_2'}]}
86
+ """
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Integration Test Example for AWS Cloud Foundations Inventory Scripts
4
+
5
+ This demonstrates how to use moto for AWS service mocking in inventory script testing.
6
+ This follows the KISS principle - simple, focused tests that validate core functionality.
7
+
8
+ **Test Strategy:**
9
+ - Use moto to mock AWS services (no real AWS calls)
10
+ - Test individual functions in isolation
11
+ - Focus on core logic validation
12
+ - Demonstrate integration testing patterns
13
+
14
+ **Markers:**
15
+ - @pytest.mark.integration: Integration tests using moto
16
+ - @pytest.mark.aws_service: AWS service-specific tests
17
+ - @pytest.mark.inventory: Inventory collection tests
18
+ """
19
+
20
+ import boto3
21
+ import pytest
22
+ from moto import mock_aws
23
+
24
+ # Test Markers
25
+ pytestmark = [pytest.mark.integration, pytest.mark.aws_service, pytest.mark.inventory]
26
+
27
+
28
+ class TestEC2IntegrationWithMoto:
29
+ """
30
+ Integration tests for EC2-related inventory scripts using moto.
31
+
32
+ These tests demonstrate how to mock AWS EC2 service for testing
33
+ inventory collection without requiring real AWS credentials.
34
+ """
35
+
36
+ @mock_aws
37
+ def test_ec2_describe_instances_with_mocked_service(self):
38
+ """
39
+ Test EC2 instance discovery with mocked EC2 service.
40
+
41
+ This test demonstrates:
42
+ - Moto EC2 service mocking
43
+ - Instance creation and discovery
44
+ - Basic inventory collection patterns
45
+ """
46
+ # Create mock EC2 client
47
+ ec2_client = boto3.client("ec2", region_name="us-east-1")
48
+
49
+ # Create test instances
50
+ response = ec2_client.run_instances(
51
+ ImageId="ami-12345678",
52
+ MinCount=2,
53
+ MaxCount=2,
54
+ InstanceType="t2.micro",
55
+ TagSpecifications=[
56
+ {
57
+ "ResourceType": "instance",
58
+ "Tags": [
59
+ {"Key": "Environment", "Value": "test"},
60
+ {"Key": "Project", "Value": "inventory-testing"},
61
+ ],
62
+ }
63
+ ],
64
+ )
65
+
66
+ # Test instance discovery
67
+ instances_response = ec2_client.describe_instances()
68
+ reservations = instances_response["Reservations"]
69
+
70
+ # Validate results
71
+ assert len(reservations) == 1, "Should have one reservation"
72
+ instances = reservations[0]["Instances"]
73
+ assert len(instances) == 2, "Should have two instances"
74
+
75
+ # Validate instance properties
76
+ for instance in instances:
77
+ assert instance["InstanceType"] == "t2.micro"
78
+ assert instance["State"]["Name"] == "running"
79
+
80
+ # Check tags
81
+ tags = {tag["Key"]: tag["Value"] for tag in instance.get("Tags", [])}
82
+ assert tags["Environment"] == "test"
83
+ assert tags["Project"] == "inventory-testing"
84
+
85
+ @mock_aws
86
+ def test_vpc_discovery_with_moto(self):
87
+ """
88
+ Test VPC discovery with mocked EC2 service.
89
+
90
+ Demonstrates VPC and subnet inventory collection patterns.
91
+ """
92
+ ec2_client = boto3.client("ec2", region_name="us-west-2")
93
+
94
+ # Create test VPC
95
+ vpc_response = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")
96
+ vpc_id = vpc_response["Vpc"]["VpcId"]
97
+
98
+ # Tag the VPC
99
+ ec2_client.create_tags(
100
+ Resources=[vpc_id], Tags=[{"Key": "Name", "Value": "test-vpc"}, {"Key": "Environment", "Value": "testing"}]
101
+ )
102
+
103
+ # Create test subnet
104
+ subnet_response = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.1.0/24")
105
+ subnet_id = subnet_response["Subnet"]["SubnetId"]
106
+
107
+ # Test VPC discovery
108
+ vpcs_response = ec2_client.describe_vpcs()
109
+ vpcs = vpcs_response["Vpcs"]
110
+
111
+ # Validate VPC discovery
112
+ test_vpc = next((vpc for vpc in vpcs if vpc["VpcId"] == vpc_id), None)
113
+ assert test_vpc is not None, "Test VPC should be discovered"
114
+ assert test_vpc["CidrBlock"] == "10.0.0.0/16"
115
+
116
+ # Test subnet discovery
117
+ subnets_response = ec2_client.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
118
+ subnets = subnets_response["Subnets"]
119
+
120
+ assert len(subnets) == 1, "Should find one subnet"
121
+ assert subnets[0]["SubnetId"] == subnet_id
122
+ assert subnets[0]["CidrBlock"] == "10.0.1.0/24"
123
+
124
+
125
+ class TestOrganizationsIntegrationWithMoto:
126
+ """
127
+ Integration tests for AWS Organizations functionality using moto.
128
+
129
+ These tests demonstrate testing patterns for organization structure
130
+ discovery and policy management.
131
+ """
132
+
133
+ @mock_aws
134
+ def test_organizations_basic_structure(self):
135
+ """
136
+ Test basic AWS Organizations structure discovery.
137
+
138
+ Note: moto's Organizations support is limited, but this demonstrates
139
+ the testing pattern for when more features become available.
140
+ """
141
+ org_client = boto3.client("organizations", region_name="us-east-1")
142
+
143
+ try:
144
+ # Attempt to create organization (may not be fully supported in moto)
145
+ response = org_client.create_organization(FeatureSet="ALL")
146
+
147
+ # Test root discovery
148
+ roots_response = org_client.list_roots()
149
+ roots = roots_response["Roots"]
150
+
151
+ assert len(roots) >= 1, "Should have at least one root"
152
+
153
+ except Exception as e:
154
+ # Expected - moto has limited Organizations support
155
+ # This demonstrates the test structure for when support improves
156
+ pytest.skip(f"Moto Organizations support limited: {e}")
157
+
158
+
159
+ class TestIAMIntegrationWithMoto:
160
+ """
161
+ Integration tests for IAM-related inventory scripts using moto.
162
+
163
+ Demonstrates testing patterns for IAM role and policy discovery.
164
+ """
165
+
166
+ @mock_aws
167
+ def test_iam_role_discovery(self):
168
+ """
169
+ Test IAM role discovery with mocked IAM service.
170
+
171
+ This demonstrates testing patterns for IAM inventory collection.
172
+ """
173
+ iam_client = boto3.client("iam", region_name="us-east-1")
174
+
175
+ # Create test role
176
+ import json
177
+
178
+ assume_role_policy = {
179
+ "Version": "2012-10-17",
180
+ "Statement": [
181
+ {"Effect": "Allow", "Principal": {"Service": "ec2.amazonaws.com"}, "Action": "sts:AssumeRole"}
182
+ ],
183
+ }
184
+
185
+ iam_client.create_role(
186
+ RoleName="test-inventory-role",
187
+ AssumeRolePolicyDocument=json.dumps(assume_role_policy),
188
+ Description="Test role for inventory testing",
189
+ Tags=[{"Key": "Environment", "Value": "test"}, {"Key": "Purpose", "Value": "inventory-testing"}],
190
+ )
191
+
192
+ # Test role discovery
193
+ roles_response = iam_client.list_roles()
194
+ roles = roles_response["Roles"]
195
+
196
+ # Find test role
197
+ test_role = next((role for role in roles if role["RoleName"] == "test-inventory-role"), None)
198
+
199
+ assert test_role is not None, "Test role should be discovered"
200
+ assert test_role["Description"] == "Test role for inventory testing"
201
+
202
+ @mock_aws
203
+ def test_iam_policy_discovery(self):
204
+ """
205
+ Test IAM managed policy discovery patterns.
206
+ """
207
+ iam_client = boto3.client("iam", region_name="us-east-1")
208
+
209
+ # Create test policy
210
+ import json
211
+
212
+ policy_document = {
213
+ "Version": "2012-10-17",
214
+ "Statement": [{"Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::test-bucket/*"}],
215
+ }
216
+
217
+ policy_response = iam_client.create_policy(
218
+ PolicyName="test-inventory-policy",
219
+ PolicyDocument=json.dumps(policy_document),
220
+ Description="Test policy for inventory testing",
221
+ )
222
+
223
+ policy_arn = policy_response["Policy"]["Arn"]
224
+
225
+ # Test policy discovery
226
+ policies_response = iam_client.list_policies(Scope="Local")
227
+ policies = policies_response["Policies"]
228
+
229
+ # Find test policy
230
+ test_policy = next((policy for policy in policies if policy["Arn"] == policy_arn), None)
231
+
232
+ assert test_policy is not None, "Test policy should be discovered"
233
+ assert test_policy["PolicyName"] == "test-inventory-policy"
234
+
235
+
236
+ class TestArgumentParsingValidation:
237
+ """
238
+ Unit tests for CLI argument parsing validation.
239
+
240
+ These tests validate argument parsing logic without requiring AWS services.
241
+ """
242
+
243
+ def test_common_argument_patterns(self):
244
+ """
245
+ Test common argument parsing patterns used across inventory scripts.
246
+
247
+ This demonstrates testing CLI argument parsing in isolation.
248
+ """
249
+ # Test data for common argument patterns
250
+ test_cases = [
251
+ {
252
+ "args": ["--profile", "test-profile", "--verbose"],
253
+ "expected_profile": "test-profile",
254
+ "expected_verbose": True,
255
+ },
256
+ {
257
+ "args": ["--region", "us-west-2", "--output", "json"],
258
+ "expected_region": "us-west-2",
259
+ "expected_output": "json",
260
+ },
261
+ ]
262
+
263
+ # This would normally test actual argument parsing functions
264
+ # For now, demonstrates the testing pattern
265
+ for case in test_cases:
266
+ # Mock argument parsing would go here
267
+ assert isinstance(case["args"], list)
268
+ assert len(case["args"]) >= 2
269
+
270
+
271
+ if __name__ == "__main__":
272
+ # Allow running individual test file
273
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,49 @@
1
+ from datetime import datetime
2
+
3
+ import pytest
4
+ from botocore import client
5
+ from common_test_data import cli_provided_parameters1, cli_provided_parameters2, get_all_my_orgs_test_result_dict
6
+ from common_test_functions import AWSAccount_from_AWSKeyID, _amend_make_api_call, _amend_make_api_call_orig
7
+ from org_list_accounts import all_my_orgs
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ "parameters,test_value_dict",
12
+ [
13
+ (cli_provided_parameters1, get_all_my_orgs_test_result_dict),
14
+ (cli_provided_parameters2, get_all_my_orgs_test_result_dict),
15
+ # str(1993),
16
+ # json.dumps({"SecretString": "my-secret"}),
17
+ # json.dumps([2, 3, 5, 7, 11, 13, 17, 19]),
18
+ # KeyError("How dare you touch my secret!"),
19
+ # ValueError("Oh my goodness you even have the guts to repeat it!!!"),
20
+ ],
21
+ )
22
+ def test_get_account_data(parameters, test_value_dict, mocker):
23
+ pProfiles = parameters["pProfiles"]
24
+ pSkipProfiles = parameters["pSkipProfiles"]
25
+ pAccountList = parameters["pAccountList"]
26
+ pTiming = parameters["pTiming"]
27
+ pRootOnly = parameters["pRootOnly"]
28
+ pSaveFilename = parameters["pSaveFilename"]
29
+ pShortform = parameters["pShortform"]
30
+ pverbose = parameters["pverbose"]
31
+ test_data = {"FunctionName": "all_my_orgs", "AccountSpecific": True, "RegionSpecific": True}
32
+ # _amend_make_api_call_orig(pProfiles[0], test_value, mocker)
33
+ _amend_make_api_call(test_data, test_value_dict, mocker)
34
+
35
+ # if isinstance(test_value_dict, Exception):
36
+ # print("Expected Error...")
37
+ # with pytest.raises(type(test_value_dict)) as error:
38
+ # all_my_orgs(pProfiles, pSkipProfiles, pAccountList, pTiming, pRootOnly, pSaveFilename, pShortform, pverbose)
39
+ # result = error
40
+ # else:
41
+
42
+ # During the run of this test, the profile provided MUST match something real on the user's desktop,
43
+ # otherwise the script thinks the profile is unmatched and doesn't try to use it.
44
+ # In further testing, I'll have to figure out how to mock the session call, since it's not being caught by the "make_api_call" function...
45
+ result = all_my_orgs(
46
+ pProfiles, pSkipProfiles, pAccountList, pTiming, pRootOnly, pSaveFilename, pShortform, pverbose
47
+ )
48
+
49
+ print("Result:", result)
@@ -0,0 +1,173 @@
1
+ import logging
2
+ import sys
3
+ from datetime import datetime
4
+ from os.path import split
5
+ from queue import Queue
6
+ from threading import Thread
7
+ from time import time
8
+
9
+ import boto3
10
+ from ArgumentsClass import CommonArguments
11
+ from colorama import Fore, Style, init
12
+ from tqdm.auto import tqdm, trange
13
+
14
+ __version__ = "2024.04.24"
15
+
16
+ from tqdm import trange
17
+
18
+ init()
19
+
20
+
21
+ ##################
22
+ # Functions
23
+ ##################
24
+
25
+
26
+ def parse_args(f_arguments):
27
+ """
28
+ Description: Parses the arguments passed into the script
29
+ @param f_arguments: args represents the list of arguments passed in
30
+ @return: returns an object namespace that contains the individualized parameters passed in
31
+ """
32
+ script_path, script_name = split(sys.argv[0])
33
+ parser = CommonArguments()
34
+ parser.my_parser.description = "We're going to find all API actions within the available AWS services."
35
+ parser.timing()
36
+ parser.save_to_file()
37
+ parser.verbosity()
38
+ parser.version(__version__)
39
+ local = parser.my_parser.add_argument_group(script_name, "Parameters specific to this script")
40
+ # local.add_argument(
41
+ # "-s", "--status",
42
+ # dest="pStatus",
43
+ # choices=['running', 'stopped'],
44
+ # type=str,
45
+ # default=None,
46
+ # help="Whether you want to limit the instances returned to either 'running', 'stopped'. Default is both")
47
+ return parser.my_parser.parse_args(f_arguments)
48
+
49
+
50
+ def random_string(stringLength=10):
51
+ """
52
+ Description: Generates a random string
53
+ @param stringLength: to determine the length of the random number generated
54
+ @return: returns a random string of characters of length "stringlength"
55
+ """
56
+ import random
57
+ import string
58
+
59
+ # Generate a random string of fixed length
60
+ letters = string.ascii_lowercase
61
+ randomstring = "".join(random.choice(letters) for _ in range(stringLength))
62
+ return randomstring
63
+
64
+
65
+ def get_aws_actions():
66
+ """
67
+ Description: Gets all the actions for all the services in AWS
68
+ @return: list of actions
69
+ """
70
+
71
+ class AWSActionThread(Thread):
72
+ def __init__(self, queue):
73
+ Thread.__init__(self)
74
+ self.queue = queue
75
+
76
+ def run(self):
77
+ while True:
78
+ # existing code to get actions for a service
79
+ c_service = self.queue.get()
80
+ client = boto3.client(c_service)
81
+ try:
82
+ list_of_operations = client.meta.method_to_api_mapping.keys()
83
+ # print(f"{ERASE_LINE}Checking actions for {c_service}", end='\r', flush=True)
84
+ for operation in tqdm(list_of_operations, desc=f"actions for {c_service}", leave=False):
85
+ action_list.append({"Service": c_service, "Operation": operation})
86
+ except AttributeError as myError:
87
+ print(myError)
88
+ action_list.append({"Service": c_service, "Operation": None})
89
+ pass
90
+ finally:
91
+ self.queue.task_done()
92
+
93
+ checkqueue = Queue()
94
+ action_list = []
95
+ workerthreads = 10
96
+
97
+ for x in trange(
98
+ workerthreads,
99
+ desc=f"Creating {workerthreads} worker threads",
100
+ leave=False,
101
+ # , position=0
102
+ ):
103
+ worker = AWSActionThread(checkqueue)
104
+ worker.daemon = True
105
+ worker.start()
106
+
107
+ # Create a thread for every service.
108
+ print(f"Capturing all available AWS Services...")
109
+ for service in tqdm(
110
+ list_of_services,
111
+ desc=f"Checking actions for each service",
112
+ # , position=0
113
+ ):
114
+ # for service in list_of_services:
115
+ checkqueue.put(service)
116
+ checkqueue.join()
117
+
118
+ return action_list
119
+
120
+
121
+ def save_file(file_to_save_to: str = None, input_data: list = None):
122
+ """
123
+ Description: Saves the data to a file
124
+ @param file_to_save_to:
125
+ @param input_data:
126
+ @return: None
127
+ """
128
+ if input_data is None:
129
+ print(f"Input data cannot be none. Exiting...")
130
+ raise SystemExit(1)
131
+ elif len(input_data) == 0:
132
+ print(f"No data to save. Exiting...")
133
+ raise SystemExit(1)
134
+
135
+ if file_to_save_to is None:
136
+ print(f"No filename provided to save data.\nUsing a randomized name.")
137
+ file_to_save_to = random_string(15) + ".txt"
138
+ else:
139
+ logging.info(f"Saving data to {file_to_save_to}")
140
+ with open(file_to_save_to, "w", encoding="utf-8") as f:
141
+ for item in input_data:
142
+ f.write(f"{item['Service']}:{item['Operation']}\n")
143
+ return file_to_save_to
144
+
145
+
146
+ ##################
147
+ # Main
148
+ ##################
149
+ ERASE_LINE = "\x1b[2K"
150
+ begin_time = time()
151
+
152
+ if __name__ == "__main__":
153
+ arguments = parse_args(sys.argv[1:])
154
+ pTiming = arguments.Time
155
+ file_to_save = arguments.Filename
156
+ verbose = arguments.loglevel
157
+ logging.basicConfig(level=verbose, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
158
+ logging.getLogger("boto3").setLevel(logging.CRITICAL)
159
+ logging.getLogger("botocore").setLevel(logging.CRITICAL)
160
+ logging.getLogger("s3transfer").setLevel(logging.CRITICAL)
161
+ logging.getLogger("urllib3").setLevel(logging.CRITICAL)
162
+
163
+ list_of_services = boto3.Session().get_available_services()
164
+ all_actions = get_aws_actions()
165
+ if pTiming:
166
+ print(ERASE_LINE)
167
+ print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
168
+ print(ERASE_LINE)
169
+ print(f"Found {len(all_actions)} actions across {len(list_of_services)} services")
170
+ filename = save_file(file_to_save, all_actions)
171
+ print(f"Saved to {filename}")
172
+ print("Thank you for using this script")
173
+ print()