aws-inventory-manager 0.13.2__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.

Potentially problematic release.


This version of aws-inventory-manager might be problematic. Click here for more details.

Files changed (145) hide show
  1. aws_inventory_manager-0.13.2.dist-info/LICENSE +21 -0
  2. aws_inventory_manager-0.13.2.dist-info/METADATA +1226 -0
  3. aws_inventory_manager-0.13.2.dist-info/RECORD +145 -0
  4. aws_inventory_manager-0.13.2.dist-info/WHEEL +5 -0
  5. aws_inventory_manager-0.13.2.dist-info/entry_points.txt +2 -0
  6. aws_inventory_manager-0.13.2.dist-info/top_level.txt +1 -0
  7. src/__init__.py +3 -0
  8. src/aws/__init__.py +11 -0
  9. src/aws/client.py +128 -0
  10. src/aws/credentials.py +191 -0
  11. src/aws/rate_limiter.py +177 -0
  12. src/cli/__init__.py +12 -0
  13. src/cli/config.py +130 -0
  14. src/cli/main.py +3626 -0
  15. src/config_service/__init__.py +21 -0
  16. src/config_service/collector.py +346 -0
  17. src/config_service/detector.py +256 -0
  18. src/config_service/resource_type_mapping.py +328 -0
  19. src/cost/__init__.py +5 -0
  20. src/cost/analyzer.py +226 -0
  21. src/cost/explorer.py +209 -0
  22. src/cost/reporter.py +237 -0
  23. src/delta/__init__.py +5 -0
  24. src/delta/calculator.py +206 -0
  25. src/delta/differ.py +185 -0
  26. src/delta/formatters.py +272 -0
  27. src/delta/models.py +154 -0
  28. src/delta/reporter.py +234 -0
  29. src/models/__init__.py +21 -0
  30. src/models/config_diff.py +135 -0
  31. src/models/cost_report.py +87 -0
  32. src/models/deletion_operation.py +104 -0
  33. src/models/deletion_record.py +97 -0
  34. src/models/delta_report.py +122 -0
  35. src/models/efs_resource.py +80 -0
  36. src/models/elasticache_resource.py +90 -0
  37. src/models/group.py +318 -0
  38. src/models/inventory.py +133 -0
  39. src/models/protection_rule.py +123 -0
  40. src/models/report.py +288 -0
  41. src/models/resource.py +111 -0
  42. src/models/security_finding.py +102 -0
  43. src/models/snapshot.py +122 -0
  44. src/restore/__init__.py +20 -0
  45. src/restore/audit.py +175 -0
  46. src/restore/cleaner.py +461 -0
  47. src/restore/config.py +209 -0
  48. src/restore/deleter.py +976 -0
  49. src/restore/dependency.py +254 -0
  50. src/restore/safety.py +115 -0
  51. src/security/__init__.py +0 -0
  52. src/security/checks/__init__.py +0 -0
  53. src/security/checks/base.py +56 -0
  54. src/security/checks/ec2_checks.py +88 -0
  55. src/security/checks/elasticache_checks.py +149 -0
  56. src/security/checks/iam_checks.py +102 -0
  57. src/security/checks/rds_checks.py +140 -0
  58. src/security/checks/s3_checks.py +95 -0
  59. src/security/checks/secrets_checks.py +96 -0
  60. src/security/checks/sg_checks.py +142 -0
  61. src/security/cis_mapper.py +97 -0
  62. src/security/models.py +53 -0
  63. src/security/reporter.py +174 -0
  64. src/security/scanner.py +87 -0
  65. src/snapshot/__init__.py +6 -0
  66. src/snapshot/capturer.py +451 -0
  67. src/snapshot/filter.py +259 -0
  68. src/snapshot/inventory_storage.py +236 -0
  69. src/snapshot/report_formatter.py +250 -0
  70. src/snapshot/reporter.py +189 -0
  71. src/snapshot/resource_collectors/__init__.py +5 -0
  72. src/snapshot/resource_collectors/apigateway.py +140 -0
  73. src/snapshot/resource_collectors/backup.py +136 -0
  74. src/snapshot/resource_collectors/base.py +81 -0
  75. src/snapshot/resource_collectors/cloudformation.py +55 -0
  76. src/snapshot/resource_collectors/cloudwatch.py +109 -0
  77. src/snapshot/resource_collectors/codebuild.py +69 -0
  78. src/snapshot/resource_collectors/codepipeline.py +82 -0
  79. src/snapshot/resource_collectors/dynamodb.py +65 -0
  80. src/snapshot/resource_collectors/ec2.py +240 -0
  81. src/snapshot/resource_collectors/ecs.py +215 -0
  82. src/snapshot/resource_collectors/efs_collector.py +102 -0
  83. src/snapshot/resource_collectors/eks.py +200 -0
  84. src/snapshot/resource_collectors/elasticache_collector.py +79 -0
  85. src/snapshot/resource_collectors/elb.py +126 -0
  86. src/snapshot/resource_collectors/eventbridge.py +156 -0
  87. src/snapshot/resource_collectors/iam.py +188 -0
  88. src/snapshot/resource_collectors/kms.py +111 -0
  89. src/snapshot/resource_collectors/lambda_func.py +139 -0
  90. src/snapshot/resource_collectors/rds.py +109 -0
  91. src/snapshot/resource_collectors/route53.py +86 -0
  92. src/snapshot/resource_collectors/s3.py +105 -0
  93. src/snapshot/resource_collectors/secretsmanager.py +70 -0
  94. src/snapshot/resource_collectors/sns.py +68 -0
  95. src/snapshot/resource_collectors/sqs.py +82 -0
  96. src/snapshot/resource_collectors/ssm.py +160 -0
  97. src/snapshot/resource_collectors/stepfunctions.py +74 -0
  98. src/snapshot/resource_collectors/vpcendpoints.py +79 -0
  99. src/snapshot/resource_collectors/waf.py +159 -0
  100. src/snapshot/storage.py +351 -0
  101. src/storage/__init__.py +21 -0
  102. src/storage/audit_store.py +419 -0
  103. src/storage/database.py +294 -0
  104. src/storage/group_store.py +749 -0
  105. src/storage/inventory_store.py +320 -0
  106. src/storage/resource_store.py +413 -0
  107. src/storage/schema.py +288 -0
  108. src/storage/snapshot_store.py +346 -0
  109. src/utils/__init__.py +12 -0
  110. src/utils/export.py +305 -0
  111. src/utils/hash.py +60 -0
  112. src/utils/logging.py +63 -0
  113. src/utils/pagination.py +41 -0
  114. src/utils/paths.py +51 -0
  115. src/utils/progress.py +41 -0
  116. src/utils/unsupported_resources.py +306 -0
  117. src/web/__init__.py +5 -0
  118. src/web/app.py +97 -0
  119. src/web/dependencies.py +69 -0
  120. src/web/routes/__init__.py +1 -0
  121. src/web/routes/api/__init__.py +18 -0
  122. src/web/routes/api/charts.py +156 -0
  123. src/web/routes/api/cleanup.py +186 -0
  124. src/web/routes/api/filters.py +253 -0
  125. src/web/routes/api/groups.py +305 -0
  126. src/web/routes/api/inventories.py +80 -0
  127. src/web/routes/api/queries.py +202 -0
  128. src/web/routes/api/resources.py +379 -0
  129. src/web/routes/api/snapshots.py +314 -0
  130. src/web/routes/api/views.py +260 -0
  131. src/web/routes/pages.py +198 -0
  132. src/web/services/__init__.py +1 -0
  133. src/web/templates/base.html +949 -0
  134. src/web/templates/components/navbar.html +31 -0
  135. src/web/templates/components/sidebar.html +104 -0
  136. src/web/templates/pages/audit_logs.html +86 -0
  137. src/web/templates/pages/cleanup.html +279 -0
  138. src/web/templates/pages/dashboard.html +227 -0
  139. src/web/templates/pages/diff.html +175 -0
  140. src/web/templates/pages/error.html +30 -0
  141. src/web/templates/pages/groups.html +721 -0
  142. src/web/templates/pages/queries.html +246 -0
  143. src/web/templates/pages/resources.html +2251 -0
  144. src/web/templates/pages/snapshot_detail.html +271 -0
  145. src/web/templates/pages/snapshots.html +429 -0
@@ -0,0 +1,145 @@
1
+ src/__init__.py,sha256=DMK4jB1kbfaPjMeyFvozB7wOQmpsX41md4fINtVJsyk,75
2
+ src/aws/__init__.py,sha256=FcFY2cn8ZQCnActrZYH1hD0x4uFkM9bMBDOmuHA3_mw,253
3
+ src/aws/client.py,sha256=CjqCxEsbgxQYWx_HF6g9_W-pfXl7yr1Z9armXyzuYpM,4089
4
+ src/aws/credentials.py,sha256=mxj8lQf7TRG5dznQJ37rGQqfB-viHh_5VCt1C4xgqG0,6374
5
+ src/aws/rate_limiter.py,sha256=b3dV4A9ftUsGgVMOQmv3IVO0Fkym2o1QDHKpFOdSYbg,5464
6
+ src/cli/__init__.py,sha256=zstPcm000JdpuUuMRuqL2TkC-bnCpm0syHjAyu8ELcY,387
7
+ src/cli/config.py,sha256=to_hVDxQh0WNANPDQKYlnZzCsFS6_bnrE745wZgRQe4,4286
8
+ src/cli/main.py,sha256=7g1AFowwBXZQj0PrShQxq3FlLTRwUp6e7U-CiezMPFY,147763
9
+ src/config_service/__init__.py,sha256=fqYfceBP_kPnp8PjYW9D_FqsWqrrOzsQTftBgp7-aTE,585
10
+ src/config_service/collector.py,sha256=uW6m1feceYCOgANGWvxhbw23etcaqSQpmdIu-DTsWuk,12254
11
+ src/config_service/detector.py,sha256=BoJC32DXN2f8x8U8RiOmjkGtnrTQrRuO568FHcMt_xU,8531
12
+ src/config_service/resource_type_mapping.py,sha256=DyxmPcP0vQ2ouZ0hRS-oXUcViSNYSfzwXYuvoUu8cu8,8726
13
+ src/cost/__init__.py,sha256=WpTwsWAm8wyCkB65gsCnzu89-r5G_hgrbL9zTqvQ8j8,122
14
+ src/cost/analyzer.py,sha256=02WFkZErD-zfu1Jp8UDym3wTjuB0c3l5jadZmbGbBJk,10201
15
+ src/cost/explorer.py,sha256=c4Yj7w5gmvoeD9iY7hseinj3Ij-g9Rh8YXWYZVkO6u4,6847
16
+ src/cost/reporter.py,sha256=JPisT4C8YIt3vaQ1wD4VOAsRu5STNhp5Grs0ugQ0zA4,9609
17
+ src/delta/__init__.py,sha256=ypHqn-VKswWWisVQC1sY_23VqHC_LIaSdQv6HIwYU4Y,120
18
+ src/delta/calculator.py,sha256=5KKUKYiCQdTtfqB84llwej52Ev0r9B5SZfJ4aNuq37E,7833
19
+ src/delta/differ.py,sha256=-7MESqWyO8srPhs9OlsLJjYx57qPwQRh93GsatqnoW8,6692
20
+ src/delta/formatters.py,sha256=xhzph_iLGLD8yyflYCMTFZqWPmW9tB7hZsVHsgQDrzM,9209
21
+ src/delta/models.py,sha256=_biiC136e-C2GljlIeW7ql5vC47Ss1M8a-jgw9K9rFg,5103
22
+ src/delta/reporter.py,sha256=yqzof9snkJyJiOeGoLAla2pmnFNIPlR3brg7xrstpNA,8775
23
+ src/models/__init__.py,sha256=5DFL_8ZgLE28EG8ORumMT3EfhFAxXrFa3yzQpQBSQxc,533
24
+ src/models/config_diff.py,sha256=elh4oUgoln_sDC6lmkqfSd8k1pX9oqBPqTEgTksw5f8,4237
25
+ src/models/cost_report.py,sha256=BzyWrMewHau7EpQyLm4WoesVY3WDPBOtGVdk_H8USuM,3079
26
+ src/models/deletion_operation.py,sha256=bb2TiY_UsHEBk-B6qp1_miXOePTphhIAWRMnhYrh3k8,3468
27
+ src/models/deletion_record.py,sha256=OxA3oDRKhGct_D0apaOBg5lPQqc5CdICswpXipCrnes,3311
28
+ src/models/delta_report.py,sha256=TD0Jk2I9D5Fw2oXEkN6oUwMzGeIVfQzsRY-LK_MR8to,4854
29
+ src/models/efs_resource.py,sha256=Jq8bzDKEYo8vB6M-Zz1RVowteD0nyxUhF8C6gumWe7E,2665
30
+ src/models/elasticache_resource.py,sha256=WRHOjZ7m4cT7OwfL8GQp5Ays9S8i9RLGynkAr5aAB3o,3201
31
+ src/models/group.py,sha256=suBBtO4ipRjZP1XptNfcMMkERcAtYTYf3YIkcYYRoeU,11452
32
+ src/models/inventory.py,sha256=hEg5qQy_K8HvgtM7AyU1y1MX6c9xzsOefbCShCaH8PI,5157
33
+ src/models/protection_rule.py,sha256=DjGJ6il9y6fPe5WTZzlq0BJwfAOyQf6vEwmT-SjDYsc,3929
34
+ src/models/report.py,sha256=Qb9OKdPNRFxcfxk-9vFEnVctKaVWgg4aP-QTEBgJlxo,9288
35
+ src/models/resource.py,sha256=jTpdJabrn0KRU6aGEiJZyUtZFTvXIDNgPDWx5MmIVZs,3882
36
+ src/models/security_finding.py,sha256=RyTobHFg6n0eomCDVuL89zYevpmSInneZ4RtDHtms9U,3451
37
+ src/models/snapshot.py,sha256=-kj9LYSrZSfSY44sc-TyJBNfjCYgegol8uCTXSJbNx8,4841
38
+ src/restore/__init__.py,sha256=iTghVKlfBxzzIxaun_AafOXHNLbyMwimJGtSwZZIyYY,563
39
+ src/restore/audit.py,sha256=XQH7iBrEFfSsewBesa677DZw1YXaqLqbvwByDq2vuTc,6538
40
+ src/restore/cleaner.py,sha256=o_4jeLgHIku54rKkiNNVowdVw5LY8UdxTT9ikQnnAx0,16589
41
+ src/restore/config.py,sha256=6MiqaIo_0x5V3F8XRKTvu24ij15IUiuQku39otySU7s,6536
42
+ src/restore/deleter.py,sha256=mqsg0w6vWeGBqVcvz82VjsHTeAltgS65Kevxv0eie_o,41483
43
+ src/restore/dependency.py,sha256=WNEgJAQHqpOF6ir9cjiUtGVbckQfRvToOWeJCtzR76U,9190
44
+ src/restore/safety.py,sha256=_LIlkcJdCJCXx9aopZuRrd18R8Ag-rv5G-xG20TOf14,3971
45
+ src/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ src/security/cis_mapper.py,sha256=rLSQobH1a1fa_tc9nQpaOPGSx1Wt7vlomxSyyyNwdqI,3147
47
+ src/security/models.py,sha256=IASZUo9_35W3nwnehmpiW-X19WaJANYOpB_OUHEltHk,1579
48
+ src/security/reporter.py,sha256=oe84rm0-FRhUgIHERyozkWIJpg7LIA-TPsxG8FiZOGw,5543
49
+ src/security/scanner.py,sha256=JGWHYZJ2VHXzas6nvsqSlkd3PsU6dg6VnAfJZABmvEY,3293
50
+ src/security/checks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
+ src/security/checks/base.py,sha256=2e-i55dVO-0N8squB6vUg5DEPZnTvW6XPpLPQLW5s0I,1371
52
+ src/security/checks/ec2_checks.py,sha256=hUO1cTN-zd45WEGy7xZIzb7LMOjdggTc8YfLyEVA38M,3113
53
+ src/security/checks/elasticache_checks.py,sha256=CGkF-6e5Z7iCKj3dvghyMsU9aF9abnLs7Xu7MPFvy4I,6108
54
+ src/security/checks/iam_checks.py,sha256=k-kSf4zmVZ7svrajt_-jPW5bDGyLrswAX2E9KhfqXbU,3952
55
+ src/security/checks/rds_checks.py,sha256=oRmflzwLf3_JeMEJt-pCoPKGFgwC2BdItzuUQYyyXEo,5112
56
+ src/security/checks/s3_checks.py,sha256=usHrGBmmIZc4PEDSMRRE1H8A_NwUxV2kiIVeLaPCauc,3387
57
+ src/security/checks/secrets_checks.py,sha256=2bJY0Hqb1PwUYid5XZCC6y4hgWle3sRtwYNVh4jmUBw,3631
58
+ src/security/checks/sg_checks.py,sha256=BmipY31beybajxSbS_HoDkSXFYTQFhjXWNxuYYPEP2U,4731
59
+ src/snapshot/__init__.py,sha256=trHfRe_8jxjQVb48cDLVH3gs2Y7YQtsX2gFL4OB9qRc,237
60
+ src/snapshot/capturer.py,sha256=DZqDnyeum9m4AzSH-jj6wrz5I3QpHcdAHWRAE-LL4qc,17975
61
+ src/snapshot/filter.py,sha256=UPVTi23NHJYKFTD4ss8PazDHh-NIC2lWLvjJnr9Y7QY,9158
62
+ src/snapshot/inventory_storage.py,sha256=eOwGzYhUrvnv_usgEaUF-0_cDGGf21k-vjRRKP7Z5Hs,7739
63
+ src/snapshot/report_formatter.py,sha256=WR7Adqq5FjJqu3iIWAnOZ9yp6AuJH1kA6uKKpT7jrT4,8266
64
+ src/snapshot/reporter.py,sha256=CZp5fj6RcHoD5a3-1unwq5KKIxrQA38rLWCjspnyvvI,6628
65
+ src/snapshot/storage.py,sha256=29zyNelW4QeH0C5E-GYnD5N4sQvyXtiZgPG8OYw4R7c,11826
66
+ src/snapshot/resource_collectors/__init__.py,sha256=QSJiStvbmqd_uCI6c4T03-3PMb0UmxAROZwVHPk25gc,94
67
+ src/snapshot/resource_collectors/apigateway.py,sha256=X916NdfRzUMNmMuyJdB_BrgTu1p7P7NwslPxWcPW4CU,4748
68
+ src/snapshot/resource_collectors/backup.py,sha256=mWySnU_47Se88uJN33scZovpUYPYwGRNMo3ur_yyqT0,4795
69
+ src/snapshot/resource_collectors/base.py,sha256=kpn0gRbf6PBb_1CA-0e6lnaI7jCm7mXPEsFwVNGSY-k,2369
70
+ src/snapshot/resource_collectors/cloudformation.py,sha256=YqMwzEF9neoXebRRfN0s8BT8ex66hWOPJQXk0aaLmxQ,1892
71
+ src/snapshot/resource_collectors/cloudwatch.py,sha256=JW4RMWl4hD_NTuAWtOV4v5FIPRY-opKuJeIecKxmzWI,4095
72
+ src/snapshot/resource_collectors/codebuild.py,sha256=VQjw-KSu4lA5kN7ci63vaphpPIgY4ZelYaFWTV7uD7M,2485
73
+ src/snapshot/resource_collectors/codepipeline.py,sha256=-1Dait_7Yzz-a_B9OtbsKNNzovbns0YKuFSifY0b5OU,3372
74
+ src/snapshot/resource_collectors/dynamodb.py,sha256=QRtEH5uLwrqF0yBIeHX_RLnOYovSnbcdTy6oo4xg9ZY,2463
75
+ src/snapshot/resource_collectors/ec2.py,sha256=hvor4Z0ofznR8sLvrK_OdvNh7-mvuDZeDOXPr1-X-qg,8441
76
+ src/snapshot/resource_collectors/ecs.py,sha256=90uj0FBU9uF03OZ7RkkS1Mc3g5YSz0v2hWYCOYS5NXw,8379
77
+ src/snapshot/resource_collectors/efs_collector.py,sha256=58ouqtCF16bxDO66-M1LojyVpJASyhv6D7RJ2KQC3DQ,4015
78
+ src/snapshot/resource_collectors/eks.py,sha256=T7Rh4ij3frxYgIfAcZNk0xNpoahwBfsQWcs14xcB5rE,7746
79
+ src/snapshot/resource_collectors/elasticache_collector.py,sha256=g7mVa9ZlE0DBITnA-i565REZRpumWMnqZuLVHcVO0Gw,2830
80
+ src/snapshot/resource_collectors/elb.py,sha256=af3IeKUv_FUfcLwYcnDajh4_4eSWtelvoazKCptehnQ,5125
81
+ src/snapshot/resource_collectors/eventbridge.py,sha256=abHxh0Iob6YrumdzmFugpRGWFqE0uVRVt89gajdq5q4,6062
82
+ src/snapshot/resource_collectors/iam.py,sha256=N5QQO7E5opV-3CiOuoW_W0x-m_2alOjYs0-Q_loS-rQ,6732
83
+ src/snapshot/resource_collectors/kms.py,sha256=ioJUO2C7XawrGGnvpNA1pVT0GdHDI4pmbeqpACgf7Dc,4524
84
+ src/snapshot/resource_collectors/lambda_func.py,sha256=bjlWj_orHlU7vVT91c9ZcEAsXSPB7f4jgUWAMfa3C1w,5289
85
+ src/snapshot/resource_collectors/rds.py,sha256=Q4kdYZ4eDyLwAVTTM49PWh8Gg1NII-NBiY4_h5YafKw,4127
86
+ src/snapshot/resource_collectors/route53.py,sha256=oCFoEba-f80cf6e4R69Ovzp0eDgAZl_nNx4AkZ1L7zk,3311
87
+ src/snapshot/resource_collectors/s3.py,sha256=de90KYNHZ3b9UZ0csJlrw-Z5p1VgtU4sHyF0-5eft34,3749
88
+ src/snapshot/resource_collectors/secretsmanager.py,sha256=0daVuZE_VEk185boKM09Pc0A7E4b83jRztWYNMQm6bM,2673
89
+ src/snapshot/resource_collectors/sns.py,sha256=rsRyA95iBkildU2ytNeKihxOzwnwpUGkgZON2MOLTrY,2539
90
+ src/snapshot/resource_collectors/sqs.py,sha256=BK4LZJWjP7y2sItVEnn1-OHmDyNlhKlp--Bs2YIw7Mk,3112
91
+ src/snapshot/resource_collectors/ssm.py,sha256=EhyeGfK_17cqhDGuEM-KBg6TpAYj4kSNUvuNnfpeWes,6258
92
+ src/snapshot/resource_collectors/stepfunctions.py,sha256=I8TYniEzSX2jKZbSX6WI02eet5uP5ekn63uKnMaStEc,2950
93
+ src/snapshot/resource_collectors/vpcendpoints.py,sha256=tclcLPRxFmE5vwQQPwFQv2nbOc-4qVFnUaSJ4oDMU7w,2994
94
+ src/snapshot/resource_collectors/waf.py,sha256=HUNshQ_WHbIK3nAyNwhb21uHkMC7ywSOke28b-5ECRg,6461
95
+ src/storage/__init__.py,sha256=4LrT-bm45zLK6VUyKsx3jyK_L270A48t-w2-0ceb9PM,554
96
+ src/storage/audit_store.py,sha256=zO7L9o-8N31PsiLJ72wvmodoDrFtFeya05Ebfes8a5Y,14123
97
+ src/storage/database.py,sha256=j-IJ4LiNs7PgXH0L3FtMlmbId1TaGjNCcYjgn3dt7sQ,9465
98
+ src/storage/group_store.py,sha256=6sCqKwFTNHUOin08V1AXlN2scam2khSwf_cgisvD-M0,24832
99
+ src/storage/inventory_store.py,sha256=hRxdL2oqDTvtIuyNiR9IoBFjH1jOe3DV43C8Ybt-aTU,10601
100
+ src/storage/resource_store.py,sha256=MB8JVvlq5zZJcjLbIL6R6dtPfqK6hpV0JhzSjiofez0,13111
101
+ src/storage/schema.py,sha256=PFDVbC12RGh-VP1CNLqFc6V_QCmblGkf63VCaHuPKGU,10274
102
+ src/storage/snapshot_store.py,sha256=TI87NQmpMRG1lXcx8HvcKGPJuOyhzH0WmaUK0C2blZc,11855
103
+ src/utils/__init__.py,sha256=1qjjL3wxB7ye6s3AT4h5hQ9Xgi7y0WllUMmTpiZWC54,284
104
+ src/utils/export.py,sha256=lBUxLGkvtdzTFq2VKRmHgf18ZROiorHGGHlbeG7ixxc,8929
105
+ src/utils/hash.py,sha256=w6jJqNR64IEcgMIrIY-M1QXuo_CkH0sbT8I6JcFQJqo,1747
106
+ src/utils/logging.py,sha256=D39u9xrDSr_HlqedKa2yz-vmbCKulNAkJNVfG571UXY,2558
107
+ src/utils/pagination.py,sha256=BoMk2lP65-tGP1U6aUAuJPU7M-JroSvyfpScLKcGYxg,1153
108
+ src/utils/paths.py,sha256=EgO41-XE1KOmF0drY2YlJkzNQIb3WPsrGtPvsc0hVqY,1607
109
+ src/utils/progress.py,sha256=H8_6AtVsw-_-P40E57ECofkFw0v1Lq6uEVSofuxCXxY,936
110
+ src/utils/unsupported_resources.py,sha256=wN2fkN4Vpq2X_CXhpN2K0ewojeghrY3H4ARAL6lvCk4,10349
111
+ src/web/__init__.py,sha256=JIdxc2oP_DBW6BK8MFeYMCGGI-DO1K0ybUkML36Dg1A,100
112
+ src/web/app.py,sha256=G-cVVCakvBI3ErsHfE_RYznrjm6dnOeTbd0x6glypT4,2819
113
+ src/web/dependencies.py,sha256=Fp2J05-9rFYshs-fN_mlTPFPqpOsp5V3ilI5ReqwW6c,1736
114
+ src/web/routes/__init__.py,sha256=sFFDPD60smOOMalYMdiSFy0cmTM8co00A3ZZiP-915Q,26
115
+ src/web/routes/pages.py,sha256=TZjTMgQw88cpakJsYf4D3HdCTC5PgdoWXLH-zXg6oCs,5717
116
+ src/web/routes/api/__init__.py,sha256=q1ysdpJ1-3-Ux5sM3eH9nJCXya5AjeB077KnClVj1CI,712
117
+ src/web/routes/api/charts.py,sha256=V-2MtmsihptC9eikpw_u2VbJIyhMl-stUEO6QT27mSA,4136
118
+ src/web/routes/api/cleanup.py,sha256=27HOlGZJa4lh5cbuBWuVbtUgji7YVka-ojjswG-vhz8,6695
119
+ src/web/routes/api/filters.py,sha256=ILQmkxtpQjRh1XsRSmzet62Mfg-uVfl6UfLCDKzEqhM,7708
120
+ src/web/routes/api/groups.py,sha256=5ipRMBU8xpQHYtu29aJwfOmHYUMhu3kZWNxWVIRrQvg,8890
121
+ src/web/routes/api/inventories.py,sha256=dN2WSLlQB2HrDwOM9XdpM3dpoC2zDDE1V8O-Anculv4,2541
122
+ src/web/routes/api/queries.py,sha256=vETfCAaLk_HX9IQNbmKpc5XD4-cbs3UYrQA_uGjzkYw,5444
123
+ src/web/routes/api/resources.py,sha256=C2lXj3FqFmptIeUTWChJEEG8Z5MC_R-LSPUBrSX1tJg,12958
124
+ src/web/routes/api/snapshots.py,sha256=rZHifGAB8abop1iungV8ubYE7bwMf5u55zyypbHy-uo,9860
125
+ src/web/routes/api/views.py,sha256=MSFr6woupQA3-ftwv0zhwNadlYlQAsBzTjfb3o-KwGg,7843
126
+ src/web/services/__init__.py,sha256=CdQNRtr3v5VA5wpZ2d4SqhxwtfaDacxs29IYrVlaCFg,28
127
+ src/web/templates/base.html,sha256=EueO9AnupiS52R-jmLU35DkUC_7Bel24QyGRWVFaUK0,34109
128
+ src/web/templates/components/navbar.html,sha256=GX-rtm2vDSAcvY-lGaXvemTT04m9X5OIm9W60STl4Do,1997
129
+ src/web/templates/components/sidebar.html,sha256=TCclf5c3ss0f_D9P4H7R_uJLoqUxkUzTInCJZQ1f5No,9131
130
+ src/web/templates/pages/audit_logs.html,sha256=0shhlDlQ0Ghlxqu7wlAbSvEwv15G1V3gSratDVu1_nY,4930
131
+ src/web/templates/pages/cleanup.html,sha256=SzJPTY10ax_9T1XEM0juFLf1b5dyHcKHPQTIAUn-0cg,16204
132
+ src/web/templates/pages/dashboard.html,sha256=t6FKnpdkk6Um4JrtE5IJKF81KRMifVttCp09HjddXGs,10581
133
+ src/web/templates/pages/diff.html,sha256=UeRqKd6Op2XXNkNg916km7KICMPngh8FVxiaSQuxoO8,8489
134
+ src/web/templates/pages/error.html,sha256=rCnXvdqhrXQX8RungpL35q6iAU77ntQTnC-B52zYkdo,1715
135
+ src/web/templates/pages/groups.html,sha256=N2Bj44Bx5hTiXap9b_dIPjan_CS_9pAqJeJYAs5o-z0,43176
136
+ src/web/templates/pages/queries.html,sha256=sf167CjkCQmx0jZQsEorgSO2N3zVyISdEIpxkhZVr7E,12443
137
+ src/web/templates/pages/resources.html,sha256=fygTKEcER7e_L2jDKZD6G3MewWFWBvIHGMbBxW8GqMY,123801
138
+ src/web/templates/pages/snapshot_detail.html,sha256=hHoM_smOhVnDhsqHRrjv_Bk3V2WsUcsjOhFEy6qqDDs,12457
139
+ src/web/templates/pages/snapshots.html,sha256=3b47IQgkN4mxJpqjUaJ_lPqYUqM2Icz44Ad8CesZpvM,22536
140
+ aws_inventory_manager-0.13.2.dist-info/LICENSE,sha256=-lY65BqqcGV9QVjIoTpYEB0Jaddm9j-cS5ICDRBRQo0,1071
141
+ aws_inventory_manager-0.13.2.dist-info/METADATA,sha256=F4QepgckeuJ9n529xrQrLlBBztr7uzSuNTchDF_sxtw,42531
142
+ aws_inventory_manager-0.13.2.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
143
+ aws_inventory_manager-0.13.2.dist-info/entry_points.txt,sha256=Ktdhto-PER5BtwWKYBtU_-FS3ngiGtAGGeoLzRW2qUw,44
144
+ aws_inventory_manager-0.13.2.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
145
+ aws_inventory_manager-0.13.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (76.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ awsinv = src.cli:cli_main
src/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """AWS Baseline Snapshot & Delta Tracking tool."""
2
+
3
+ __version__ = "0.13.1"
src/aws/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """AWS client utilities and wrappers."""
2
+
3
+ from .client import create_boto_client
4
+ from .credentials import validate_credentials
5
+ from .rate_limiter import RateLimiter
6
+
7
+ __all__ = [
8
+ "create_boto_client",
9
+ "validate_credentials",
10
+ "RateLimiter",
11
+ ]
src/aws/client.py ADDED
@@ -0,0 +1,128 @@
1
+ """Boto3 client wrapper with retry configuration and error handling."""
2
+
3
+ import logging
4
+ from typing import Any, List, Optional
5
+
6
+ import boto3
7
+ from botocore.config import Config
8
+ from botocore.exceptions import ClientError, NoCredentialsError
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ # Aggressive retry configuration for batch operations
14
+ DEFAULT_RETRY_CONFIG = Config(
15
+ retries={"max_attempts": 10, "mode": "adaptive"}, # Adaptive retry mode (boto3 1.16+)
16
+ max_pool_connections=50,
17
+ connect_timeout=60,
18
+ read_timeout=60,
19
+ )
20
+
21
+
22
+ def create_boto_client(
23
+ service_name: str,
24
+ region_name: str = "us-east-1",
25
+ profile_name: Optional[str] = None,
26
+ retry_config: Optional[Config] = None,
27
+ ) -> Any:
28
+ """Create boto3 client with retry configuration and error handling.
29
+
30
+ Args:
31
+ service_name: AWS service name (e.g., 'ec2', 'iam', 'lambda')
32
+ region_name: AWS region (default: 'us-east-1')
33
+ profile_name: AWS profile name from ~/.aws/config (optional)
34
+ retry_config: Custom botocore Config object (optional)
35
+
36
+ Returns:
37
+ Configured boto3 client
38
+
39
+ Raises:
40
+ NoCredentialsError: If AWS credentials are not found
41
+ ClientError: If client creation fails
42
+ """
43
+ if retry_config is None:
44
+ retry_config = DEFAULT_RETRY_CONFIG
45
+
46
+ try:
47
+ # Create session (with or without profile)
48
+ if profile_name:
49
+ session = boto3.Session(profile_name=profile_name)
50
+ client = session.client(service_name, region_name=region_name, config=retry_config)
51
+ else:
52
+ client = boto3.client(service_name, region_name=region_name, config=retry_config)
53
+
54
+ logger.debug(f"Created {service_name} client for region {region_name}")
55
+ return client
56
+
57
+ except NoCredentialsError:
58
+ logger.error("AWS credentials not found")
59
+ raise
60
+ except ClientError as e:
61
+ logger.error(f"Failed to create {service_name} client: {e}")
62
+ raise
63
+ except Exception as e:
64
+ logger.error(f"Unexpected error creating {service_name} client: {e}")
65
+ raise
66
+
67
+
68
+ def get_enabled_regions(profile_name: Optional[str] = None) -> List[str]: # type: ignore
69
+ """Get list of enabled AWS regions for the account.
70
+
71
+ Args:
72
+ profile_name: AWS profile name (optional)
73
+
74
+ Returns:
75
+ List of enabled region names
76
+ """
77
+ try:
78
+ ec2_client = create_boto_client("ec2", region_name="us-east-1", profile_name=profile_name)
79
+ response = ec2_client.describe_regions(AllRegions=False) # Only enabled regions
80
+ regions = [region["RegionName"] for region in response["Regions"]]
81
+ logger.info(f"Found {len(regions)} enabled regions")
82
+ return regions
83
+ except Exception as e:
84
+ logger.warning(f"Could not retrieve enabled regions: {e}")
85
+ # Return default set of common regions
86
+ return [
87
+ "us-east-1",
88
+ "us-east-2",
89
+ "us-west-1",
90
+ "us-west-2",
91
+ "eu-west-1",
92
+ "eu-central-1",
93
+ "ap-southeast-1",
94
+ "ap-northeast-1",
95
+ ]
96
+
97
+
98
+ def check_client_connection(client: Any) -> bool:
99
+ """Test if a boto3 client can connect to AWS.
100
+
101
+ Args:
102
+ client: Boto3 client instance
103
+
104
+ Returns:
105
+ True if connection successful, False otherwise
106
+ """
107
+ try:
108
+ # Different services have different test methods
109
+ service_name = client._service_model.service_name
110
+
111
+ if service_name == "sts":
112
+ client.get_caller_identity()
113
+ elif service_name == "ec2":
114
+ client.describe_regions(MaxResults=1)
115
+ elif service_name == "iam":
116
+ client.list_users(MaxItems=1)
117
+ elif service_name == "lambda":
118
+ client.list_functions(MaxItems=1)
119
+ elif service_name == "s3":
120
+ client.list_buckets()
121
+ else:
122
+ # Generic test - just try to get service metadata
123
+ client._make_api_call("ListObjects", {})
124
+
125
+ return True
126
+ except Exception as e:
127
+ logger.debug(f"Client connection test failed: {e}")
128
+ return False
src/aws/credentials.py ADDED
@@ -0,0 +1,191 @@
1
+ """AWS credential validation and permission checking."""
2
+
3
+ import logging
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import boto3
7
+ from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class CredentialValidationError(Exception):
13
+ """Raised when AWS credentials are invalid or missing."""
14
+
15
+ pass
16
+
17
+
18
+ def validate_credentials(profile_name: Optional[str] = None) -> Dict[str, Any]:
19
+ """Validate AWS credentials and return caller identity information.
20
+
21
+ Args:
22
+ profile_name: AWS profile name from ~/.aws/config (optional)
23
+
24
+ Returns:
25
+ Dictionary with account ID, user ID, and ARN
26
+
27
+ Raises:
28
+ CredentialValidationError: If credentials are invalid or missing
29
+ """
30
+ try:
31
+ if profile_name:
32
+ session = boto3.Session(profile_name=profile_name)
33
+ sts_client = session.client("sts")
34
+ else:
35
+ sts_client = boto3.client("sts")
36
+
37
+ # Get caller identity to validate credentials
38
+ identity = sts_client.get_caller_identity()
39
+
40
+ logger.debug(f"Validated credentials for account {identity['Account']}")
41
+
42
+ return {
43
+ "account_id": identity["Account"],
44
+ "user_id": identity["UserId"],
45
+ "arn": identity["Arn"],
46
+ }
47
+
48
+ except NoCredentialsError:
49
+ error_msg = (
50
+ "AWS credentials not found. Please configure credentials using one of these methods:\n"
51
+ " 1. Run: aws configure\n"
52
+ " 2. Set environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY\n"
53
+ " 3. Use --profile option with a configured profile\n\n"
54
+ "For more info: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html"
55
+ )
56
+ logger.error("No AWS credentials found")
57
+ raise CredentialValidationError(error_msg)
58
+
59
+ except PartialCredentialsError as e:
60
+ error_msg = f"Incomplete AWS credentials: {e}"
61
+ logger.error(error_msg)
62
+ raise CredentialValidationError(error_msg)
63
+
64
+ except ClientError as e:
65
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
66
+ if error_code == "InvalidClientTokenId":
67
+ error_msg = "AWS credentials are invalid. Please check your access key ID."
68
+ elif error_code == "SignatureDoesNotMatch":
69
+ error_msg = "AWS credentials signature mismatch. Please check your secret access key."
70
+ elif error_code == "ExpiredToken":
71
+ error_msg = "AWS credentials have expired. Please refresh your temporary credentials."
72
+ else:
73
+ error_msg = f"AWS credential validation failed: {e}"
74
+
75
+ logger.error(error_msg)
76
+ raise CredentialValidationError(error_msg)
77
+
78
+ except Exception as e:
79
+ error_msg = f"Unexpected error validating credentials: {e}"
80
+ logger.error(error_msg)
81
+ raise CredentialValidationError(error_msg)
82
+
83
+
84
+ def check_required_permissions(
85
+ profile_name: Optional[str] = None, required_actions: Optional[List[str]] = None
86
+ ) -> Dict[str, bool]:
87
+ """Check if credentials have required IAM permissions.
88
+
89
+ Note: This is a best-effort check using IAM policy simulation.
90
+ Some permissions may not be accurately detected.
91
+
92
+ Args:
93
+ profile_name: AWS profile name (optional)
94
+ required_actions: List of IAM actions to check (e.g., ['ec2:DescribeInstances'])
95
+
96
+ Returns:
97
+ Dictionary mapping action names to permission status (True/False)
98
+ """
99
+ if required_actions is None:
100
+ # Default minimum required permissions for snapshot operations
101
+ required_actions = [
102
+ "ec2:DescribeInstances",
103
+ "ec2:DescribeRegions",
104
+ "iam:ListRoles",
105
+ "lambda:ListFunctions",
106
+ "s3:ListAllMyBuckets",
107
+ ]
108
+
109
+ try:
110
+ # Get caller identity first
111
+ identity = validate_credentials(profile_name)
112
+
113
+ if profile_name:
114
+ session = boto3.Session(profile_name=profile_name)
115
+ iam_client = session.client("iam")
116
+ else:
117
+ iam_client = boto3.client("iam")
118
+
119
+ results = {}
120
+
121
+ # Try to simulate policy for each action
122
+ for action in required_actions:
123
+ try:
124
+ response = iam_client.simulate_principal_policy(
125
+ PolicySourceArn=identity["arn"],
126
+ ActionNames=[action],
127
+ )
128
+
129
+ # Check if action is allowed
130
+ eval_results = response.get("EvaluationResults", [])
131
+ if eval_results:
132
+ decision = eval_results[0].get("EvalDecision", "deny")
133
+ results[action] = decision.lower() == "allowed"
134
+ else:
135
+ results[action] = False
136
+
137
+ except ClientError as e:
138
+ # If simulation fails (e.g., lack of iam:SimulatePrincipalPolicy permission),
139
+ # we can't determine the permission status
140
+ logger.debug(f"Could not check permission for {action}: {e}")
141
+ results[action] = None # Unknown
142
+
143
+ return results
144
+
145
+ except Exception as e:
146
+ logger.warning(f"Permission check failed: {e}")
147
+ return {action: None for action in required_actions} # type: ignore
148
+
149
+
150
+ def get_account_id(profile_name: Optional[str] = None) -> str:
151
+ """Get AWS account ID for the current credentials.
152
+
153
+ Args:
154
+ profile_name: AWS profile name (optional)
155
+
156
+ Returns:
157
+ 12-digit AWS account ID
158
+
159
+ Raises:
160
+ CredentialValidationError: If credentials are invalid
161
+ """
162
+ identity = validate_credentials(profile_name)
163
+ return identity["account_id"]
164
+
165
+
166
+ def get_credential_summary(profile_name: Optional[str] = None) -> str:
167
+ """Get a human-readable summary of current AWS credentials.
168
+
169
+ Args:
170
+ profile_name: AWS profile name (optional)
171
+
172
+ Returns:
173
+ Formatted string with credential information
174
+ """
175
+ try:
176
+ identity = validate_credentials(profile_name)
177
+
178
+ summary = f"""
179
+ AWS Credentials Valid
180
+ Account ID: {identity['account_id']}
181
+ User/Role: {identity['arn'].split('/')[-1]}
182
+ ARN: {identity['arn']}
183
+ """
184
+
185
+ if profile_name:
186
+ summary += f"Profile: {profile_name}\n"
187
+
188
+ return summary.strip()
189
+
190
+ except CredentialValidationError as e:
191
+ return f"Credential Validation Failed:\n{e}"
@@ -0,0 +1,177 @@
1
+ """Rate limiter utility using token bucket algorithm."""
2
+
3
+ import logging
4
+ import time
5
+ from threading import Lock
6
+ from typing import Dict, Optional
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ # Service-specific rate limits (calls per second)
12
+ # Based on AWS API throttling limits
13
+ SERVICE_RATE_LIMITS: Dict[str, float] = {
14
+ "iam": 5.0, # IAM has strict rate limits (global service)
15
+ "cloudformation": 2.0, # CloudFormation is particularly slow
16
+ "sts": 10.0, # STS is also rate-limited
17
+ "default": 10.0, # Conservative default for other services
18
+ }
19
+
20
+
21
+ class RateLimiter:
22
+ """Token bucket rate limiter for controlling API call frequency.
23
+
24
+ This prevents hitting AWS API rate limits by throttling client-side
25
+ before the request is even made.
26
+ """
27
+
28
+ def __init__(self, rate: float):
29
+ """Initialize rate limiter.
30
+
31
+ Args:
32
+ rate: Maximum number of calls per second
33
+ """
34
+ self.rate = rate
35
+ self.tokens = rate
36
+ self.last_update = time.time()
37
+ self.lock = Lock()
38
+
39
+ logger.debug(f"Initialized rate limiter with rate {rate} calls/sec")
40
+
41
+ def acquire(self, blocking: bool = True) -> bool:
42
+ """Acquire permission to make an API call.
43
+
44
+ This method will block until a token is available (if blocking=True)
45
+ or return immediately (if blocking=False).
46
+
47
+ Args:
48
+ blocking: If True, wait until token available. If False, return immediately.
49
+
50
+ Returns:
51
+ True if token acquired, False if blocking=False and no token available
52
+ """
53
+ with self.lock:
54
+ now = time.time()
55
+ elapsed = now - self.last_update
56
+
57
+ # Refill tokens based on elapsed time
58
+ self.tokens = min(self.rate, self.tokens + elapsed * self.rate)
59
+ self.last_update = now
60
+
61
+ if self.tokens >= 1:
62
+ # Token available
63
+ self.tokens -= 1
64
+ return True
65
+ else:
66
+ # No token available
67
+ if not blocking:
68
+ return False
69
+
70
+ # Calculate how long to sleep
71
+ sleep_time = (1 - self.tokens) / self.rate
72
+ logger.debug(f"Rate limiter sleeping for {sleep_time:.3f}s")
73
+
74
+ # Sleep outside the lock to allow other threads
75
+ time.sleep(sleep_time)
76
+
77
+ # Acquire token after sleeping
78
+ with self.lock:
79
+ self.tokens = 0
80
+ self.last_update = time.time()
81
+ return True
82
+
83
+ def try_acquire(self) -> bool:
84
+ """Try to acquire a token without blocking.
85
+
86
+ Returns:
87
+ True if token acquired, False otherwise
88
+ """
89
+ return self.acquire(blocking=False)
90
+
91
+
92
+ class ServiceRateLimiter:
93
+ """Manages rate limiters for different AWS services."""
94
+
95
+ def __init__(self, rate_limits: Optional[Dict[str, float]] = None):
96
+ """Initialize service rate limiter.
97
+
98
+ Args:
99
+ rate_limits: Dictionary mapping service names to rates (optional)
100
+ """
101
+ self.rate_limits = rate_limits or SERVICE_RATE_LIMITS
102
+ self._limiters: Dict[str, RateLimiter] = {}
103
+ self._lock = Lock()
104
+
105
+ def get_limiter(self, service_name: str) -> RateLimiter:
106
+ """Get or create a rate limiter for a service.
107
+
108
+ Args:
109
+ service_name: AWS service name (e.g., 'iam', 'ec2')
110
+
111
+ Returns:
112
+ RateLimiter instance for the service
113
+ """
114
+ with self._lock:
115
+ if service_name not in self._limiters:
116
+ rate = self.rate_limits.get(service_name, self.rate_limits["default"])
117
+ self._limiters[service_name] = RateLimiter(rate)
118
+ logger.debug(f"Created rate limiter for {service_name} ({rate} calls/sec)")
119
+
120
+ return self._limiters[service_name]
121
+
122
+ def acquire(self, service_name: str, blocking: bool = True) -> bool:
123
+ """Acquire permission to call an AWS service API.
124
+
125
+ Args:
126
+ service_name: AWS service name
127
+ blocking: Whether to block until token available
128
+
129
+ Returns:
130
+ True if token acquired
131
+ """
132
+ limiter = self.get_limiter(service_name)
133
+ return limiter.acquire(blocking=blocking)
134
+
135
+ def try_acquire(self, service_name: str) -> bool:
136
+ """Try to acquire permission without blocking.
137
+
138
+ Args:
139
+ service_name: AWS service name
140
+
141
+ Returns:
142
+ True if token acquired, False otherwise
143
+ """
144
+ return self.acquire(service_name, blocking=False)
145
+
146
+
147
+ # Global service rate limiter instance
148
+ _global_limiter: Optional[ServiceRateLimiter] = None
149
+
150
+
151
+ def get_global_rate_limiter() -> ServiceRateLimiter:
152
+ """Get the global service rate limiter instance.
153
+
154
+ Returns:
155
+ Global ServiceRateLimiter instance
156
+ """
157
+ global _global_limiter
158
+ if _global_limiter is None:
159
+ _global_limiter = ServiceRateLimiter()
160
+ return _global_limiter
161
+
162
+
163
+ def rate_limited_call(service_name: str, func, *args, **kwargs): # type: ignore[no-untyped-def]
164
+ """Execute a function with rate limiting applied.
165
+
166
+ Args:
167
+ service_name: AWS service name for rate limiting
168
+ func: Function to call
169
+ *args: Positional arguments to pass to func
170
+ **kwargs: Keyword arguments to pass to func
171
+
172
+ Returns:
173
+ Result of func(*args, **kwargs)
174
+ """
175
+ limiter = get_global_rate_limiter()
176
+ limiter.acquire(service_name)
177
+ return func(*args, **kwargs)