atlas-init 0.8.1__tar.gz → 0.10.0__tar.gz

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 (158) hide show
  1. {atlas_init-0.8.1 → atlas_init-0.10.0}/PKG-INFO +2 -1
  2. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/__init__.py +1 -1
  3. atlas_init-0.10.0/atlas_init/cli_root/aws_clean.py +108 -0
  4. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_root/trigger.py +1 -1
  5. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/hcl/modifier2.py +51 -1
  6. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/settings/rich_utils.py +22 -0
  7. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/gen_examples.py +4 -2
  8. atlas_init-0.8.1/atlas_init/tf_ext/gen_module_readme.py → atlas_init-0.10.0/atlas_init/tf_ext/gen_readme.py +46 -26
  9. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/models.py +27 -1
  10. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/models_module.py +7 -3
  11. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/paths.py +18 -1
  12. atlas_init-0.10.0/atlas_init/tf_ext/run_tf.py +20 -0
  13. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/settings.py +8 -0
  14. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/tf_dep.py +69 -22
  15. atlas_init-0.10.0/atlas_init/tf_ext/tf_example_readme.py +392 -0
  16. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/tf_mod_gen.py +8 -25
  17. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/tf_modules.py +3 -1
  18. atlas_init-0.10.0/atlas_init/tf_ext/tf_ws.py +269 -0
  19. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/typer_app.py +3 -1
  20. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/typer_app.py +2 -1
  21. {atlas_init-0.8.1 → atlas_init-0.10.0}/pyproject.toml +1 -0
  22. {atlas_init-0.8.1 → atlas_init-0.10.0}/.gitignore +0 -0
  23. {atlas_init-0.8.1 → atlas_init-0.10.0}/LICENSE +0 -0
  24. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/__main__.py +0 -0
  25. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/atlas_init.yaml +0 -0
  26. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli.py +0 -0
  27. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_args.py +0 -0
  28. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_cfn/__init__.py +0 -0
  29. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_cfn/app.py +0 -0
  30. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_cfn/aws.py +0 -0
  31. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_cfn/cfn_parameter_finder.py +0 -0
  32. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_cfn/contract.py +0 -0
  33. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_cfn/example.py +0 -0
  34. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_cfn/files.py +0 -0
  35. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_helper/__init__.py +0 -0
  36. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_helper/go.py +0 -0
  37. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_helper/run.py +0 -0
  38. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_helper/run_manager.py +0 -0
  39. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_helper/sdk.py +0 -0
  40. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_helper/sdk_auto_changes.py +0 -0
  41. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_helper/tf_runner.py +0 -0
  42. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_root/__init__.py +0 -0
  43. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_root/go_test.py +0 -0
  44. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_root/mms_released.py +0 -0
  45. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/__init__.py +0 -0
  46. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/app.py +0 -0
  47. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/changelog.py +0 -0
  48. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/ci_tests.py +0 -0
  49. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/codegen/__init__.py +0 -0
  50. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/codegen/models.py +0 -0
  51. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/codegen/openapi_minimal.py +0 -0
  52. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/debug_logs.py +0 -0
  53. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/debug_logs_test_data.py +0 -0
  54. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/debug_logs_test_data_package_config.py +0 -0
  55. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/example_update.py +0 -0
  56. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/github_logs.py +0 -0
  57. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/go_test_run.py +0 -0
  58. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/go_test_summary.py +0 -0
  59. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/go_test_tf_error.py +0 -0
  60. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/hcl/__init__.py +0 -0
  61. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/hcl/cli.py +0 -0
  62. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/hcl/cluster_mig.py +0 -0
  63. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/hcl/modifier.py +0 -0
  64. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/hcl/parser.py +0 -0
  65. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/log_clean.py +0 -0
  66. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/mock_tf_log.py +0 -0
  67. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/openapi.py +0 -0
  68. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema.py +0 -0
  69. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_go_parser.py +0 -0
  70. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_inspection.py +0 -0
  71. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_table.py +0 -0
  72. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_table_models.py +0 -0
  73. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_v2.py +0 -0
  74. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_v2_sdk.py +0 -0
  75. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_v3.py +0 -0
  76. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_v3_sdk.py +0 -0
  77. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_v3_sdk_base.py +0 -0
  78. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cli_tf/schema_v3_sdk_create.py +0 -0
  79. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cloud/__init__.py +0 -0
  80. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/cloud/aws.py +0 -0
  81. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/crud/__init__.py +0 -0
  82. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/crud/mongo_client.py +0 -0
  83. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/crud/mongo_dao.py +0 -0
  84. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/crud/mongo_utils.py +0 -0
  85. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/html_out/__init__.py +0 -0
  86. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/html_out/md_export.py +0 -0
  87. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/humps.py +0 -0
  88. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/repos/__init__.py +0 -0
  89. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/repos/cfn.py +0 -0
  90. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/repos/go_sdk.py +0 -0
  91. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/repos/path.py +0 -0
  92. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/sdk_ext/__init__.py +0 -0
  93. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/sdk_ext/go.py +0 -0
  94. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/sdk_ext/typer_app.py +0 -0
  95. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/settings/__init__.py +0 -0
  96. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/settings/config.py +0 -0
  97. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/settings/env_vars.py +0 -0
  98. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/settings/env_vars_generated.py +0 -0
  99. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/settings/env_vars_modules.py +0 -0
  100. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/settings/interactive.py +0 -0
  101. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/settings/interactive2.py +0 -0
  102. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/settings/path.py +0 -0
  103. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/terraform.yaml +0 -0
  104. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/.terraform.lock.hcl +0 -0
  105. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/always.tf +0 -0
  106. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/main.tf +0 -0
  107. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/aws_kms/aws_kms.tf +0 -0
  108. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/aws_kms/provider.tf +0 -0
  109. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/aws_s3/aws_s3.tf +0 -0
  110. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/aws_s3/provider.tf +0 -0
  111. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/aws_vars/aws_vars.tf +0 -0
  112. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/aws_vpc/aws_vpc.tf +0 -0
  113. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/aws_vpc/provider.tf +0 -0
  114. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cfn/assume_role_services.yaml +0 -0
  115. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cfn/cfn.tf +0 -0
  116. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cfn/kms.tf +0 -0
  117. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cfn/provider.tf +0 -0
  118. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cfn/resource_actions.yaml +0 -0
  119. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cfn/variables.tf +0 -0
  120. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cloud_provider/cloud_provider.tf +0 -0
  121. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cloud_provider/provider.tf +0 -0
  122. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cluster/cluster.tf +0 -0
  123. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/cluster/provider.tf +0 -0
  124. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/encryption_at_rest/main.tf +0 -0
  125. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/encryption_at_rest/provider.tf +0 -0
  126. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/federated_vars/federated_vars.tf +0 -0
  127. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/federated_vars/provider.tf +0 -0
  128. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/project_extra/project_extra.tf +0 -0
  129. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/project_extra/provider.tf +0 -0
  130. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/stream_instance/provider.tf +0 -0
  131. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/stream_instance/stream_instance.tf +0 -0
  132. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/vpc_peering/provider.tf +0 -0
  133. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/vpc_peering/vpc_peering.tf +0 -0
  134. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/vpc_privatelink/atlas-privatelink.tf +0 -0
  135. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/vpc_privatelink/variables.tf +0 -0
  136. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/modules/vpc_privatelink/versions.tf +0 -0
  137. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/outputs.tf +0 -0
  138. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/providers.tf +0 -0
  139. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf/variables.tf +0 -0
  140. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/__init__.py +0 -0
  141. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/__main__.py +0 -0
  142. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/api_call.py +0 -0
  143. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/args.py +0 -0
  144. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/constants.py +0 -0
  145. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/gen_resource_main.py +0 -0
  146. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/gen_resource_output.py +0 -0
  147. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/gen_resource_variables.py +0 -0
  148. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/gen_versions.py +0 -0
  149. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/newres.py +0 -0
  150. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/plan_diffs.py +0 -0
  151. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/provider_schema.py +0 -0
  152. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/py_gen.py +0 -0
  153. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/schema_to_dataclass.py +0 -0
  154. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/tf_desc_gen.py +0 -0
  155. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/tf_desc_update.py +0 -0
  156. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/tf_mod_gen_provider.py +0 -0
  157. {atlas_init-0.8.1 → atlas_init-0.10.0}/atlas_init/tf_ext/tf_vars.py +0 -0
  158. {atlas_init-0.8.1 → atlas_init-0.10.0}/readme.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atlas-init
3
- Version: 0.8.1
3
+ Version: 0.10.0
4
4
  Project-URL: Documentation, https://github.com/EspenAlbert/atlas-init#readme
5
5
  Project-URL: Issues, https://github.com/EspenAlbert/atlas-init/issues
6
6
  Project-URL: Source, https://github.com/EspenAlbert/atlas-init
@@ -20,6 +20,7 @@ Requires-Dist: inflection==0.5.1
20
20
  Requires-Dist: model-lib
21
21
  Requires-Dist: motor==3.7.1
22
22
  Requires-Dist: mypy-boto3-cloudformation==1.37.22
23
+ Requires-Dist: mypy-boto3-iam>=1.40.0
23
24
  Requires-Dist: orjson==3.10.13
24
25
  Requires-Dist: pydantic-settings==2.7.1
25
26
  Requires-Dist: pydot==4.0.1
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- VERSION = "0.8.1"
3
+ VERSION = "0.10.0"
4
4
 
5
5
 
6
6
  def running_in_repo() -> bool:
@@ -0,0 +1,108 @@
1
+ import logging
2
+ from datetime import datetime, timedelta
3
+ from pathlib import Path
4
+
5
+ import humanize
6
+ import typer
7
+ from ask_shell import run_and_wait
8
+ from boto3.session import Session
9
+ from model_lib import Entity
10
+ from mypy_boto3_iam import IAMClient
11
+ from mypy_boto3_iam.type_defs import RoleTypeDef
12
+ from zero_3rdparty.datetime_utils import utc_now
13
+
14
+ from atlas_init.cloud.aws import PascalAlias
15
+ from atlas_init.settings.env_vars import init_settings
16
+ from atlas_init.typer_app import app_command
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class LastUsed(Entity):
22
+ model_config = PascalAlias
23
+ last_used_date: datetime
24
+
25
+
26
+ class IAMRole(Entity):
27
+ model_config = PascalAlias
28
+ arn: str
29
+ role_name: str
30
+ create_date: datetime
31
+ role_last_used: LastUsed | None = None
32
+
33
+
34
+ @app_command()
35
+ def aws_clean(
36
+ skip_iam_roles: bool = typer.Option(False, help="skip iam roles"),
37
+ iam_role_prefix_name: str = typer.Option(
38
+ "mongodb-atlas-test-acc-tf-",
39
+ help="prefix name of iam roles to clean",
40
+ ),
41
+ ):
42
+ init_settings()
43
+ if skip_iam_roles:
44
+ return
45
+ client: IAMClient = Session().client("iam") # pyright: ignore[reportAssignmentType]
46
+ all_roles: list[RoleTypeDef] = []
47
+ aws_account_id = run_and_wait("aws sts get-caller-identity --query Account --output text").stdout_one_line
48
+
49
+ roles_response = client.list_roles()
50
+
51
+ all_roles.extend(roles_response["Roles"])
52
+ marker = roles_response.get("Marker", "")
53
+ while marker:
54
+ roles_response = client.list_roles(Marker=marker)
55
+ all_roles.extend(roles_response["Roles"])
56
+ marker = roles_response.get("Marker", "")
57
+ total_roles = len(all_roles)
58
+ logger.info(f"found {total_roles} roles")
59
+ roles_parsed: list[IAMRole] = []
60
+ delete_if_created_before = utc_now() - timedelta(days=5)
61
+ delete_count = 0
62
+ role_names: list[str] = []
63
+ for role in all_roles:
64
+ parsed = IAMRole.model_validate(role)
65
+ roles_parsed.append(parsed)
66
+ role_name = parsed.role_name
67
+ role_names.append(role_name)
68
+ if not role_name.startswith(iam_role_prefix_name):
69
+ continue
70
+ # want to delete 'mongodb-atlas-test-acc-tf-1345851232260229574'
71
+ # want to keep 'mongodb-atlas-test-acc-tf-7973337217371171538-git-ear'?
72
+ if role_name[-1].isdigit() and parsed.create_date < delete_if_created_before:
73
+ logger.info(f"role: {parsed.arn} will be deleted")
74
+ delete_count += 1
75
+ delete_role(client, role_name)
76
+ else:
77
+ logger.info(f"skipping role: {parsed.arn}, created: {humanize.naturaltime(parsed.create_date)}")
78
+ logger.info(f"deleted {delete_count}/{total_roles} roles")
79
+ out_path = Path(f"aws_roles_{aws_account_id}.txt")
80
+ out_path.write_text("\n".join(sorted(role_names)))
81
+
82
+
83
+ def delete_role(client: IAMClient, role_name: str):
84
+ try:
85
+ attached_policies = client.list_attached_role_policies(RoleName=role_name)
86
+ for policy in attached_policies["AttachedPolicies"]:
87
+ policy_arn = policy.get("PolicyArn")
88
+ if policy_arn:
89
+ logger.info(f"detaching managed policy: {policy_arn} from role: {role_name}")
90
+ client.detach_role_policy(RoleName=role_name, PolicyArn=policy_arn)
91
+ except Exception as e:
92
+ logger.warning(f"failed to detach managed policies from role {role_name}: {e}")
93
+
94
+ # Second, delete all inline policies
95
+ try:
96
+ inline_policies = client.list_role_policies(RoleName=role_name)
97
+ for policy_name in inline_policies["PolicyNames"]:
98
+ logger.info(f"deleting inline policy: {policy_name} from role: {role_name}")
99
+ client.delete_role_policy(RoleName=role_name, PolicyName=policy_name)
100
+ except Exception as e:
101
+ logger.warning(f"failed to delete inline policies from role {role_name}: {e}")
102
+
103
+ # Finally, delete the role
104
+ try:
105
+ client.delete_role(RoleName=role_name)
106
+ logger.info(f"deleted role: {role_name}")
107
+ except Exception as e:
108
+ logger.error(f"failed to delete role {role_name}: {e}")
@@ -30,7 +30,7 @@ def create_realm_app():
30
30
  project_id = atlas_settings.MONGODB_ATLAS_PROJECT_ID
31
31
  base_url = atlas_settings.realm_url
32
32
  cluster_name = cluster_settings.MONGODB_ATLAS_CLUSTER_NAME
33
- auth_headers = login_to_realm(settings, base_url)
33
+ auth_headers = login_to_realm(atlas_settings, base_url)
34
34
  realm_settings = env_vars_cls_or_none(RealmSettings, dotenv_path=settings.env_vars_trigger)
35
35
  if realm_settings and function_exists(
36
36
  base_url,
@@ -2,10 +2,12 @@ from collections import defaultdict
2
2
  import logging
3
3
  from contextlib import suppress
4
4
  from pathlib import Path
5
- from typing import NamedTuple
5
+ from typing import Any, NamedTuple
6
6
  from lark import Token, Transformer, Tree, UnexpectedToken, v_args
7
7
  from hcl2.transformer import Attribute, DictTransformer
8
8
  from hcl2.api import reverse_transform, writes, parses
9
+ from model_lib import Entity
10
+ from pydantic import field_validator
9
11
  import rich
10
12
 
11
13
  logger = logging.getLogger(__name__)
@@ -58,6 +60,54 @@ def attribute_transfomer(attr_name: str, obj_key: str, new_value: str) -> tuple[
58
60
  return AttributeTransformer(with_meta=True), changes
59
61
 
60
62
 
63
+ _unset = object()
64
+
65
+
66
+ class TFVar(Entity):
67
+ name: str
68
+ description: str | None = ""
69
+ default: Any = _unset
70
+ type: str = ""
71
+ sensitive: bool = False
72
+
73
+ @field_validator("default", mode="before")
74
+ def unpack_token(cls, v: Any) -> Any:
75
+ if isinstance(v, Token):
76
+ return v.value.strip('"')
77
+ return v
78
+
79
+
80
+ def variable_reader_typed(tree: Tree) -> dict[str, TFVar]:
81
+ variables: dict[str, TFVar] = {}
82
+
83
+ class TFVarReader(DictTransformer):
84
+ def __init__(self, with_meta: bool = False, *, name: str):
85
+ super().__init__(with_meta)
86
+ self.kwargs: dict[str, Any] = {
87
+ "name": name,
88
+ }
89
+
90
+ def attribute(self, args: list) -> Attribute:
91
+ if len(args) == 3:
92
+ name, _, value = args
93
+ self.kwargs[name] = value
94
+ return super().attribute(args)
95
+
96
+ class BlockReader(Transformer):
97
+ @v_args(tree=True)
98
+ def block(self, block_tree: Tree) -> Tree:
99
+ current_block_name = _identifier_name(block_tree)
100
+ if current_block_name == "variable":
101
+ variable_name = token_name(block_tree.children[1])
102
+ reader = TFVarReader(name=variable_name)
103
+ reader.transform(block_tree)
104
+ variables[variable_name] = TFVar(**reader.kwargs)
105
+ return block_tree
106
+
107
+ BlockReader().transform(tree)
108
+ return variables
109
+
110
+
61
111
  def variable_reader(tree: Tree) -> dict[str, str | None]:
62
112
  """
63
113
  Reads the variable names from a parsed HCL2 tree.
@@ -1,6 +1,8 @@
1
1
  import logging
2
2
  from typing import Literal
3
3
 
4
+ from rich.console import Console
5
+ from rich.tree import Tree
4
6
  import typer
5
7
  from pydantic import BaseModel
6
8
  from rich.logging import RichHandler
@@ -62,3 +64,23 @@ def configure_logging(
62
64
  app.pretty_exceptions_show_locals = False
63
65
 
64
66
  return handler
67
+
68
+
69
+ # https://github.com/Textualize/rich/blob/8c4d3d1d50047e3aaa4140d0ffc1e0c9f1df5af4/tests/test_live.py#L11
70
+ def create_capture_console(*, width: int = 60, height: int = 80, force_terminal: bool = True) -> Console:
71
+ return Console(
72
+ width=width,
73
+ height=height,
74
+ force_terminal=force_terminal,
75
+ legacy_windows=False,
76
+ color_system=None, # use no color system to reduce complexity of output,
77
+ _environ={},
78
+ )
79
+
80
+
81
+ def tree_text(tree: Tree) -> str:
82
+ console = create_capture_console()
83
+ console.width = 10_000
84
+ console.begin_capture()
85
+ console.print(tree)
86
+ return "\n".join(line.rstrip() for line in console.end_capture().splitlines())
@@ -19,10 +19,12 @@ def _examples_casted(examples: dict) -> dict[str, ResourceAbs]:
19
19
  return examples
20
20
 
21
21
 
22
- def read_example_dirs(module_path: Path) -> list[Path]:
22
+ def read_example_dirs(examples_dir: Path) -> list[Path]:
23
+ if not examples_dir.exists():
24
+ return []
23
25
  return sorted(
24
26
  example_dir
25
- for example_dir in (module_path / "examples").glob("*")
27
+ for example_dir in examples_dir.glob("*")
26
28
  if example_dir.is_dir()
27
29
  and len(example_dir.name) > 2
28
30
  and example_dir.name[:2].isdigit()
@@ -1,11 +1,14 @@
1
+ from __future__ import annotations
1
2
  import logging
2
3
  from enum import StrEnum
4
+ from pathlib import Path
5
+ from typing import Callable, TypeAlias
3
6
 
4
7
  from ask_shell import run_and_wait
5
8
  from zero_3rdparty.file_utils import ensure_parents_write_text, update_between_markers
6
9
 
7
10
  from atlas_init.tf_ext.gen_examples import read_example_dirs
8
- from atlas_init.tf_ext.models_module import ModuleGenConfig
11
+ from atlas_init.tf_ext.models_module import EXAMPLES_DIRNAME, README_FILENAME, TERRAFORM_DOCS_CONFIG_FILENAME
9
12
 
10
13
  logger = logging.getLogger(__name__)
11
14
  _readme_disclaimer = """\
@@ -13,12 +16,13 @@ _readme_disclaimer = """\
13
16
  This Module is not meant for external consumption.
14
17
  It is part of a development PoC.
15
18
  Any usage problems will not be supported.
16
- However, if you have any ideas or feedback feel free to open a Github Issue!
19
+ However, if you have any ideas or feedback, feel free to open a Github Issue!
17
20
  """
18
21
 
19
22
 
20
23
  class ReadmeMarkers(StrEnum):
21
24
  DISCLAIMER = "DISCLAIMER"
25
+ MODULES = "MODULES"
22
26
  EXAMPLE = "TF_EXAMPLES"
23
27
  TF_DOCS = "TF_DOCS"
24
28
 
@@ -42,9 +46,23 @@ class ReadmeMarkers(StrEnum):
42
46
  def example_boilerplate(cls) -> str:
43
47
  return "\n".join(cls.marker_lines(marker_name) for marker_name in list(cls))
44
48
 
49
+ @classmethod
50
+ def readme_generators(cls) -> ReadmeGenerators:
51
+ return [
52
+ (cls.DISCLAIMER, lambda _: _readme_disclaimer),
53
+ (cls.EXAMPLE, lambda workspace: read_examples(workspace / EXAMPLES_DIRNAME)),
54
+ ]
55
+
56
+
57
+ ReadmeGenerators: TypeAlias = list[tuple[ReadmeMarkers, Callable[[Path], str]]]
45
58
 
46
- def read_examples(module: ModuleGenConfig) -> str:
47
- example_dirs = read_example_dirs(module.module_out_path)
59
+
60
+ def read_examples(examples_dir: Path) -> str:
61
+ example_dirs = read_example_dirs(examples_dir)
62
+ if not example_dirs:
63
+ return ""
64
+ # ensure the examples are formatted first
65
+ run_and_wait("terraform fmt -recursive .", cwd=examples_dir.parent, allow_non_zero_exit=True, ansi_content=False)
48
66
  content = ["# Examples"]
49
67
  for example_dir in example_dirs:
50
68
  example_name = example_dir.name
@@ -80,10 +98,10 @@ sort:
80
98
  """
81
99
 
82
100
 
83
- def terraform_docs_config_content(module: ModuleGenConfig) -> str:
101
+ def terraform_docs_config_content(readme_path: Path) -> str:
84
102
  config = _static_terraform_config
85
103
  for replacement_in, replacement_out in [
86
- ("FILENAME", module.readme_path().name),
104
+ ("FILENAME", readme_path.name),
87
105
  ("START_MARKER", ReadmeMarkers.as_start(ReadmeMarkers.TF_DOCS)),
88
106
  ("END_MARKER", ReadmeMarkers.as_end(ReadmeMarkers.TF_DOCS)),
89
107
  ]:
@@ -91,36 +109,38 @@ def terraform_docs_config_content(module: ModuleGenConfig) -> str:
91
109
  return config
92
110
 
93
111
 
94
- def generate_readme(module: ModuleGenConfig) -> str:
95
- readme_path = module.readme_path()
112
+ def generate_and_write_readme(terraform_workdir: Path, *, generators: ReadmeGenerators | None = None) -> str:
113
+ generators = generators or ReadmeMarkers.readme_generators()
114
+ readme_path = terraform_workdir / README_FILENAME
96
115
  assert readme_path.exists(), (
97
116
  f"{readme_path} does not exist, currently a boilerplate is expected, consider adding to {readme_path}\n{ReadmeMarkers.example_boilerplate()}"
98
117
  )
99
- update_between_markers(
100
- readme_path,
101
- _readme_disclaimer,
102
- ReadmeMarkers.as_start(ReadmeMarkers.DISCLAIMER),
103
- ReadmeMarkers.as_end(ReadmeMarkers.DISCLAIMER),
104
- )
105
- run_and_wait("terraform fmt -recursive .", cwd=module.module_out_path, allow_non_zero_exit=True, ansi_content=False)
106
- example_section = read_examples(module)
107
- update_between_markers(
108
- readme_path,
109
- example_section,
110
- ReadmeMarkers.as_start(ReadmeMarkers.EXAMPLE),
111
- ReadmeMarkers.as_end(ReadmeMarkers.EXAMPLE),
112
- )
113
- docs_config_path = module.terraform_docs_config_path()
118
+ for marker, generator in generators:
119
+ content = generator(terraform_workdir)
120
+ if not content:
121
+ continue
122
+ update_between_markers(
123
+ readme_path,
124
+ content,
125
+ ReadmeMarkers.as_start(marker),
126
+ ReadmeMarkers.as_end(marker),
127
+ )
128
+ generate_terraform_docs(readme_path)
129
+ logger.info(f"updated {readme_path}")
130
+ return readme_path.read_text()
131
+
132
+
133
+ def generate_terraform_docs(readme_path: Path) -> None:
134
+ docs_config_path = readme_path.parent / TERRAFORM_DOCS_CONFIG_FILENAME
114
135
  if docs_config_path.exists():
115
136
  logger.warning(f"{docs_config_path} already exists, skipping generation")
116
137
  else:
117
- config_content = terraform_docs_config_content(module)
138
+ config_content = terraform_docs_config_content(readme_path)
118
139
  ensure_parents_write_text(docs_config_path, config_content)
119
140
  logger.info(f"generated {docs_config_path}")
120
- run_and_wait(f"terraform-docs -c {docs_config_path} .", cwd=module.module_out_path)
141
+ run_and_wait(f"terraform-docs -c {docs_config_path} .", cwd=readme_path.parent)
121
142
  readme_content = _default_link_updater(readme_path.read_text())
122
143
  ensure_parents_write_text(readme_path, readme_content)
123
- return readme_path.read_text()
124
144
 
125
145
 
126
146
  def _default_link_updater(readme_content: str) -> str: # can be a global replacer for now
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
- from typing import Self
2
+ from dataclasses import dataclass, field
3
+ from typing import Iterable, Self
3
4
 
4
5
  from model_lib import Entity
5
6
  from pydantic import Field, RootModel, model_validator
@@ -30,6 +31,31 @@ def choose_next_emoji() -> str:
30
31
  return emoji
31
32
 
32
33
 
34
+ @dataclass
35
+ class EmojiCounter:
36
+ counter: int = 0
37
+ existing_emojis: dict[str, str] = field(default_factory=dict)
38
+
39
+ @property
40
+ def emoji_to_names(self) -> dict[str, str]:
41
+ return dict(zip(self.existing_emojis.values(), self.existing_emojis.keys()))
42
+
43
+ def emoji_name(self) -> Iterable[tuple[str, str]]:
44
+ src = self.emoji_to_names
45
+ for emoji in _emojii_list:
46
+ if emoji in src:
47
+ yield emoji, src[emoji]
48
+ else:
49
+ break
50
+
51
+ def get_emoji(self, name: str) -> str:
52
+ if existing := self.existing_emojis.get(name):
53
+ return existing
54
+ emoji = self.existing_emojis[name] = _emojii_list[self.counter]
55
+ self.counter += 1
56
+ return emoji
57
+
58
+
33
59
  class ModuleState(Entity):
34
60
  resource_types: set[str] = Field(default_factory=set, description="Set of resource types in the module.")
35
61
 
@@ -24,6 +24,9 @@ from atlas_init.tf_ext.py_gen import (
24
24
  from atlas_init.tf_ext.settings import RepoOut, TfExtSettings
25
25
 
26
26
  ResourceTypeT: TypeAlias = str
27
+ TERRAFORM_DOCS_CONFIG_FILENAME: str = ".terraform-docs.yml"
28
+ README_FILENAME: str = "README.md"
29
+ EXAMPLES_DIRNAME: str = "examples"
27
30
 
28
31
 
29
32
  @dataclass
@@ -253,12 +256,13 @@ class ModuleGenConfig(Entity):
253
256
  return resource_type
254
257
  raise ValueError(f"Could not resolve resource type for path {path}")
255
258
 
259
+ @property
256
260
  def readme_path(self) -> Path:
257
- return self.module_out_path / "README.md"
261
+ return self.module_out_path / README_FILENAME
258
262
 
259
263
  @property
260
264
  def examples_path(self) -> Path:
261
- return self.module_out_path / "examples"
265
+ return self.module_out_path / EXAMPLES_DIRNAME
262
266
 
263
267
  def example_name(self, name: str, example_nr: int) -> str:
264
268
  return f"{example_nr:02d}_{name}"
@@ -267,7 +271,7 @@ class ModuleGenConfig(Entity):
267
271
  return self.examples_path / name
268
272
 
269
273
  def terraform_docs_config_path(self) -> Path:
270
- return self.module_out_path / ".terraform-docs.yml"
274
+ return self.module_out_path / TERRAFORM_DOCS_CONFIG_FILENAME
271
275
 
272
276
 
273
277
  @dataclass
@@ -9,7 +9,14 @@ from model_lib import Entity
9
9
  from pydantic import Field, RootModel
10
10
  from zero_3rdparty.file_utils import iter_paths
11
11
 
12
- from atlas_init.cli_tf.hcl.modifier2 import resource_types_vars_usage, safe_parse, variable_reader, variable_usages
12
+ from atlas_init.cli_tf.hcl.modifier2 import (
13
+ TFVar,
14
+ resource_types_vars_usage,
15
+ safe_parse,
16
+ variable_reader,
17
+ variable_reader_typed,
18
+ variable_usages,
19
+ )
13
20
  from atlas_init.tf_ext.constants import ATLAS_PROVIDER_NAME, DEFAULT_EXTERNAL_SUBSTRINGS, DEFAULT_INTERNAL_SUBSTRINGS
14
21
 
15
22
  logger = logging.getLogger(__name__)
@@ -32,6 +39,16 @@ def get_example_directories(repo_path: Path, skip_names: list[str]):
32
39
  return example_dirs
33
40
 
34
41
 
42
+ def find_variables_typed(variables_tf: Path) -> dict[str, TFVar]:
43
+ if not variables_tf.exists():
44
+ return {}
45
+ tree = safe_parse(variables_tf)
46
+ if not tree:
47
+ logger.warning(f"Failed to parse {variables_tf}")
48
+ return {}
49
+ return variable_reader_typed(tree)
50
+
51
+
35
52
  def find_variables(variables_tf: Path) -> dict[str, str | None]:
36
53
  if not variables_tf.exists():
37
54
  return {}
@@ -0,0 +1,20 @@
1
+ from ask_shell import new_task, run_and_wait
2
+ from pathlib import Path
3
+
4
+
5
+ def validate_tf_workspace(
6
+ tf_workdir: Path, *, tf_cli_config_file: Path | None = None, env_extra: dict[str, str] | None = None
7
+ ):
8
+ terraform_commands = [
9
+ "terraform init",
10
+ "terraform fmt .",
11
+ "terraform validate .",
12
+ ]
13
+ env_extra = env_extra or {}
14
+ if tf_cli_config_file:
15
+ env_extra["TF_CLI_CONFIG_FILE"] = str(tf_cli_config_file)
16
+ with new_task("Terraform Module Validate Checks", total=len(terraform_commands)) as task:
17
+ for command in terraform_commands:
18
+ attempts = 3 if command == "terraform init" else 1 # terraform init can fail due to network issues
19
+ run_and_wait(command, cwd=tf_workdir, env=env_extra, attempts=attempts)
20
+ task.update(advance=1)
@@ -175,6 +175,14 @@ class TfExtSettings(StaticSettings):
175
175
  def provider_cache_dir(self, provider_name: str) -> Path:
176
176
  return self.cache_root / "provider_cache" / provider_name
177
177
 
178
+ @property
179
+ def variable_plan_resolvers_file_path(self) -> Path:
180
+ return self.static_root / "variable_plan_resolvers.yaml"
181
+
182
+ @property
183
+ def variable_plan_resolvers_dumped_file_path(self) -> Path:
184
+ return self.static_root / "variable_plan_resolvers_dumped.yaml"
185
+
178
186
 
179
187
  def init_tf_ext_settings(*, allow_empty_out_path: bool = False) -> TfExtSettings:
180
188
  settings = TfExtSettings.from_env()