atlas-init 0.6.0__py3-none-any.whl → 0.8.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.
- atlas_init/__init__.py +1 -1
- atlas_init/atlas_init.yaml +1 -0
- atlas_init/cli_args.py +19 -1
- atlas_init/cli_tf/ci_tests.py +116 -24
- atlas_init/cli_tf/example_update.py +20 -8
- atlas_init/cli_tf/go_test_run.py +14 -2
- atlas_init/cli_tf/go_test_summary.py +334 -82
- atlas_init/cli_tf/go_test_tf_error.py +20 -12
- atlas_init/cli_tf/hcl/modifier.py +22 -8
- atlas_init/cli_tf/hcl/modifier2.py +120 -0
- atlas_init/cli_tf/openapi.py +10 -6
- atlas_init/html_out/__init__.py +0 -0
- atlas_init/html_out/md_export.py +143 -0
- atlas_init/sdk_ext/__init__.py +0 -0
- atlas_init/sdk_ext/go.py +102 -0
- atlas_init/sdk_ext/typer_app.py +18 -0
- atlas_init/settings/env_vars.py +25 -3
- atlas_init/settings/env_vars_generated.py +2 -0
- atlas_init/tf/.terraform.lock.hcl +33 -33
- atlas_init/tf/modules/aws_s3/provider.tf +1 -1
- atlas_init/tf/modules/aws_vpc/provider.tf +1 -1
- atlas_init/tf/modules/cloud_provider/provider.tf +1 -1
- atlas_init/tf/modules/cluster/provider.tf +1 -1
- atlas_init/tf/modules/encryption_at_rest/provider.tf +1 -1
- atlas_init/tf/modules/federated_vars/federated_vars.tf +1 -2
- atlas_init/tf/modules/federated_vars/provider.tf +1 -1
- atlas_init/tf/modules/project_extra/provider.tf +1 -1
- atlas_init/tf/modules/stream_instance/provider.tf +1 -1
- atlas_init/tf/modules/vpc_peering/provider.tf +1 -1
- atlas_init/tf/modules/vpc_privatelink/versions.tf +1 -1
- atlas_init/tf/providers.tf +1 -1
- atlas_init/tf_ext/__init__.py +0 -0
- atlas_init/tf_ext/__main__.py +3 -0
- atlas_init/tf_ext/api_call.py +325 -0
- atlas_init/tf_ext/args.py +32 -0
- atlas_init/tf_ext/constants.py +3 -0
- atlas_init/tf_ext/gen_examples.py +141 -0
- atlas_init/tf_ext/gen_module_readme.py +131 -0
- atlas_init/tf_ext/gen_resource_main.py +195 -0
- atlas_init/tf_ext/gen_resource_output.py +71 -0
- atlas_init/tf_ext/gen_resource_variables.py +159 -0
- atlas_init/tf_ext/gen_versions.py +10 -0
- atlas_init/tf_ext/models.py +106 -0
- atlas_init/tf_ext/models_module.py +454 -0
- atlas_init/tf_ext/newres.py +90 -0
- atlas_init/tf_ext/paths.py +126 -0
- atlas_init/tf_ext/plan_diffs.py +140 -0
- atlas_init/tf_ext/provider_schema.py +199 -0
- atlas_init/tf_ext/py_gen.py +294 -0
- atlas_init/tf_ext/schema_to_dataclass.py +522 -0
- atlas_init/tf_ext/settings.py +188 -0
- atlas_init/tf_ext/tf_dep.py +324 -0
- atlas_init/tf_ext/tf_desc_gen.py +53 -0
- atlas_init/tf_ext/tf_desc_update.py +0 -0
- atlas_init/tf_ext/tf_mod_gen.py +263 -0
- atlas_init/tf_ext/tf_mod_gen_provider.py +124 -0
- atlas_init/tf_ext/tf_modules.py +395 -0
- atlas_init/tf_ext/tf_vars.py +158 -0
- atlas_init/tf_ext/typer_app.py +28 -0
- {atlas_init-0.6.0.dist-info → atlas_init-0.8.0.dist-info}/METADATA +5 -3
- {atlas_init-0.6.0.dist-info → atlas_init-0.8.0.dist-info}/RECORD +64 -31
- atlas_init-0.8.0.dist-info/entry_points.txt +5 -0
- atlas_init-0.6.0.dist-info/entry_points.txt +0 -2
- {atlas_init-0.6.0.dist-info → atlas_init-0.8.0.dist-info}/WHEEL +0 -0
- {atlas_init-0.6.0.dist-info → atlas_init-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,25 +2,25 @@
|
|
2
2
|
# Manual edits may be lost in future updates.
|
3
3
|
|
4
4
|
provider "registry.terraform.io/hashicorp/aws" {
|
5
|
-
version = "5.
|
5
|
+
version = "5.100.0"
|
6
6
|
constraints = "~> 5.0"
|
7
7
|
hashes = [
|
8
|
-
"h1:
|
9
|
-
"zh:
|
10
|
-
"zh:
|
11
|
-
"zh:
|
12
|
-
"zh:
|
13
|
-
"zh:
|
14
|
-
"zh:
|
15
|
-
"zh:95e1e4cf587cde4537ac9dfee9e94270652c812ab31fce3a431778c053abf354",
|
8
|
+
"h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=",
|
9
|
+
"zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644",
|
10
|
+
"zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2",
|
11
|
+
"zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274",
|
12
|
+
"zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b",
|
13
|
+
"zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862",
|
14
|
+
"zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342",
|
16
15
|
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
17
|
-
"zh:
|
18
|
-
"zh:
|
19
|
-
"zh:
|
20
|
-
"zh:
|
21
|
-
"zh:
|
22
|
-
"zh:
|
23
|
-
"zh:
|
16
|
+
"zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93",
|
17
|
+
"zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2",
|
18
|
+
"zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e",
|
19
|
+
"zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421",
|
20
|
+
"zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4",
|
21
|
+
"zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9",
|
22
|
+
"zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9",
|
23
|
+
"zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70",
|
24
24
|
]
|
25
25
|
}
|
26
26
|
|
@@ -103,23 +103,23 @@ provider "registry.terraform.io/hashicorp/random" {
|
|
103
103
|
}
|
104
104
|
|
105
105
|
provider "registry.terraform.io/mongodb/mongodbatlas" {
|
106
|
-
version = "1.
|
107
|
-
constraints = "1.33.0"
|
106
|
+
version = "1.37.0"
|
107
|
+
constraints = ">= 1.33.0"
|
108
108
|
hashes = [
|
109
|
-
"h1:
|
110
|
-
"zh:
|
111
|
-
"zh:
|
112
|
-
"zh:
|
113
|
-
"zh:
|
114
|
-
"zh:
|
115
|
-
"zh:
|
116
|
-
"zh:
|
117
|
-
"zh:
|
118
|
-
"zh:
|
119
|
-
"zh:
|
120
|
-
"zh:
|
121
|
-
"zh:
|
122
|
-
"zh:
|
123
|
-
"zh:
|
109
|
+
"h1:Bn3O8yBFQ25GeP7bqvBEdyLFp/ZZGtxAZI9c1KU/Wek=",
|
110
|
+
"zh:0bb3d85fa4680d804b25341838ae4a5740f59c95b677e818aaeb74957093477c",
|
111
|
+
"zh:14ec370dcdf9c8a92311179089d972a2476c8d17d15ea3cd06c1e772782dcdf6",
|
112
|
+
"zh:16f42470f48c6bf727968cf0ed0fe64dfbdaf49dee626707367073c92006c354",
|
113
|
+
"zh:363bd381dbfed2f3c9a8da2aece2242d8bf2e0a94586422624352c000bcd59d7",
|
114
|
+
"zh:6762334cebaf7a72afdf72dde4edaf3263339e271cbb3946bbaae27f67551b6b",
|
115
|
+
"zh:730006225c2bb49f717610b3f8e1a7413d137b2721de541a254af7856869a203",
|
116
|
+
"zh:9aa5e9bd25788918e6ab7764f37f63d8afe6717c259e17475eed4660b8bed409",
|
117
|
+
"zh:ae0b2ad8660bed54eb1e91da863548bb368701181874d44a989b387a90bc7022",
|
118
|
+
"zh:b2fccfc349dc720239cd348b615790ce44ca7e3ff9f3f89fb30d4a1c7fe58793",
|
119
|
+
"zh:b879a1e1e2cf6585d596da131b150dcf66dd1e937576a161b3afac8bd3ab8e8a",
|
120
|
+
"zh:d23223ac3808f05023d531748e1c718ee0c2277fce8baa0268576220f6cb83a2",
|
121
|
+
"zh:dc6e6776807113723329d730b3cbc4c9d29d0c1c985414d290a63698d8ec06f8",
|
122
|
+
"zh:dff2961bf4b8f8a9d187bfd7d03839a2c2e7c1def13e83bddf82f0cbd794425f",
|
123
|
+
"zh:f5f3fc3b0b80f76c3b535ef33c76064347e96e086b7574f73e1f3931e1ab0b4d",
|
124
124
|
]
|
125
125
|
}
|
@@ -37,7 +37,6 @@ output "env_vars" {
|
|
37
37
|
MONGODB_ATLAS_FEDERATED_GROUP_ID = var.project_id
|
38
38
|
MONGODB_ATLAS_FEDERATED_IDP_ID = data.mongodbatlas_federated_settings_org_config.current.identity_provider_id # 20 character legacy needed for PATCH on org
|
39
39
|
# MONGODB_ATLAS_FEDERATED_IDP_ID = data.mongodbatlas_federated_settings_org_config.current.okta_idp_id # used for org PATCH
|
40
|
-
MONGODB_ATLAS_FEDERATED_SETTINGS_ASSOCIATED_DOMAIN = data.mongodbatlas_federated_settings_org_config.current.domain_allow_list[0]
|
40
|
+
MONGODB_ATLAS_FEDERATED_SETTINGS_ASSOCIATED_DOMAIN = try(data.mongodbatlas_federated_settings_org_config.current.domain_allow_list[0], "no-domain-set-by-atlas-init.com")
|
41
41
|
}
|
42
|
-
|
43
42
|
}
|
atlas_init/tf/providers.tf
CHANGED
File without changes
|
@@ -0,0 +1,325 @@
|
|
1
|
+
import json
|
2
|
+
import logging
|
3
|
+
import os
|
4
|
+
from collections import defaultdict
|
5
|
+
from concurrent.futures import Future, as_completed
|
6
|
+
from functools import lru_cache
|
7
|
+
from pathlib import Path
|
8
|
+
|
9
|
+
import requests
|
10
|
+
import typer
|
11
|
+
from ask_shell import new_task, print_to_live, run_pool
|
12
|
+
from model_lib import dump, parse_model
|
13
|
+
from pydantic import BaseModel, Field, model_validator
|
14
|
+
from requests.auth import HTTPDigestAuth
|
15
|
+
from rich.markdown import Markdown
|
16
|
+
from zero_3rdparty.file_utils import ensure_parents_write_text
|
17
|
+
from zero_3rdparty.str_utils import ensure_prefix, ensure_suffix, instance_repr
|
18
|
+
|
19
|
+
from atlas_init.cli_tf.mock_tf_log import resolve_admin_api_path
|
20
|
+
from atlas_init.cli_tf.openapi import OpenapiSchema
|
21
|
+
from atlas_init.settings.env_vars import init_settings
|
22
|
+
from atlas_init.settings.env_vars_generated import AtlasSettingsWithProject
|
23
|
+
from atlas_init.settings.env_vars_modules import (
|
24
|
+
TFModuleCluster,
|
25
|
+
TFModuleFederated_Vars,
|
26
|
+
TFModuleProject_Extra,
|
27
|
+
TFModuleStream_Instance,
|
28
|
+
)
|
29
|
+
from atlas_init.settings.path import load_dotenv
|
30
|
+
from atlas_init.tf_ext.settings import TfExtSettings
|
31
|
+
|
32
|
+
logger = logging.getLogger(__name__)
|
33
|
+
|
34
|
+
ALLOWED_MISSING_VARS: set[str] = {
|
35
|
+
"alertConfigId",
|
36
|
+
"alertId",
|
37
|
+
"clientId",
|
38
|
+
"cloudProvider",
|
39
|
+
"invoiceId",
|
40
|
+
# "name",
|
41
|
+
"pipelineName",
|
42
|
+
"processId",
|
43
|
+
"username",
|
44
|
+
}
|
45
|
+
ALLOWED_ERROR_CODES: set[str] = {
|
46
|
+
"CANNOT_USE_CLUSTER_IN_SERVERLESS_INSTANCE_API",
|
47
|
+
"VALIDATION_ERROR",
|
48
|
+
"UNEXPECTED_ERROR",
|
49
|
+
"CANNOT_USE_NON_FLEX_CLUSTER_IN_FLEX_API",
|
50
|
+
"CHECKPOINTS_ONLY_ON_CONTINOUS_BACKUP",
|
51
|
+
"INCORRECT_BACKUP_API_ENDPOINT",
|
52
|
+
}
|
53
|
+
|
54
|
+
|
55
|
+
# export ATLAS_INIT_TEST_SUITES=clusterm10,s3,federated,project,stream_connection
|
56
|
+
def resolve_path_variables() -> dict[str, str]:
|
57
|
+
settings = init_settings()
|
58
|
+
env_vars_full = load_dotenv(settings.env_vars_vs_code)
|
59
|
+
atlas_settings = AtlasSettingsWithProject(**env_vars_full)
|
60
|
+
cluster_settings = TFModuleCluster(**env_vars_full)
|
61
|
+
project_settings = TFModuleProject_Extra(**env_vars_full)
|
62
|
+
stream_settings = TFModuleStream_Instance(**env_vars_full)
|
63
|
+
federated_settings = TFModuleFederated_Vars(**env_vars_full)
|
64
|
+
return {
|
65
|
+
"orgId": atlas_settings.MONGODB_ATLAS_ORG_ID,
|
66
|
+
"cloudProvider": "AWS",
|
67
|
+
"federationSettingsId": federated_settings.MONGODB_ATLAS_FEDERATION_SETTINGS_ID,
|
68
|
+
"clusterName": cluster_settings.MONGODB_ATLAS_CLUSTER_NAME,
|
69
|
+
"name": cluster_settings.MONGODB_ATLAS_CLUSTER_NAME,
|
70
|
+
"groupId": atlas_settings.MONGODB_ATLAS_PROJECT_ID,
|
71
|
+
"teamId": project_settings.MONGODB_ATLAS_TEAM_ID,
|
72
|
+
"tenantName": stream_settings.MONGODB_ATLAS_STREAM_INSTANCE_NAME,
|
73
|
+
"apiUserId": atlas_settings.MONGODB_ATLAS_PROJECT_OWNER_ID,
|
74
|
+
"username": atlas_settings.MONGODB_ATLAS_USER_EMAIL,
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
class ApiCall(BaseModel):
|
79
|
+
operation_id: str
|
80
|
+
path: str
|
81
|
+
accept_header: str = "application/vnd.atlas.2023-01-01+json"
|
82
|
+
query_args: dict[str, str] = Field(default_factory=dict)
|
83
|
+
|
84
|
+
def __str__(self):
|
85
|
+
return instance_repr(self, ["operation_id", "path"])
|
86
|
+
|
87
|
+
def path_with_variables(self, path_variables: dict[str, str]):
|
88
|
+
return self.path.format(**path_variables)
|
89
|
+
|
90
|
+
@model_validator(mode="after")
|
91
|
+
def check_path_variables(self):
|
92
|
+
self.accept_header = ensure_prefix(self.accept_header, "application/vnd.atlas.")
|
93
|
+
self.accept_header = ensure_suffix(self.accept_header, "+json")
|
94
|
+
return self
|
95
|
+
|
96
|
+
|
97
|
+
class UnresolvedPathsError(Exception):
|
98
|
+
def __init__(self, missing_var_paths: dict[str, list[str]]) -> None:
|
99
|
+
self.missing_var_paths = missing_var_paths
|
100
|
+
missing_vars_formatted = "\n".join(f"{var}: {paths}" for var, paths in missing_var_paths.items())
|
101
|
+
super().__init__(f"Failed to resolve path variables:\nMissing vars: {missing_vars_formatted}")
|
102
|
+
|
103
|
+
|
104
|
+
class ApiCalls(BaseModel):
|
105
|
+
calls: list[ApiCall] = Field(default_factory=list)
|
106
|
+
ignored_calls: list[ApiCall] = Field(default_factory=list)
|
107
|
+
path_variables: dict[str, str] = Field(default_factory=resolve_path_variables)
|
108
|
+
skip_validation: bool = False
|
109
|
+
|
110
|
+
@model_validator(mode="after")
|
111
|
+
def check_path_variables(self):
|
112
|
+
if self.skip_validation:
|
113
|
+
return self
|
114
|
+
missing_vars_paths: dict[str, list[str]] = defaultdict(list)
|
115
|
+
ok_calls = []
|
116
|
+
for call in self.calls:
|
117
|
+
try:
|
118
|
+
call.path_with_variables(self.path_variables)
|
119
|
+
ok_calls.append(call)
|
120
|
+
except KeyError as e:
|
121
|
+
missing_vars_paths[str(e).strip("'")].append(f"{call.operation_id} {call.path}")
|
122
|
+
self.ignored_calls.append(call)
|
123
|
+
continue
|
124
|
+
for allowed_missing in sorted(ALLOWED_MISSING_VARS):
|
125
|
+
if allowed_missing in missing_vars_paths:
|
126
|
+
logger.info(f"Allowed missing variable {allowed_missing}: {missing_vars_paths[allowed_missing]}")
|
127
|
+
del missing_vars_paths[allowed_missing]
|
128
|
+
if missing_vars_paths:
|
129
|
+
raise UnresolvedPathsError(missing_var_paths=missing_vars_paths)
|
130
|
+
self.calls = ok_calls
|
131
|
+
return self
|
132
|
+
|
133
|
+
def dump_to_dict(self) -> dict:
|
134
|
+
return {
|
135
|
+
"calls": [call.model_dump(exclude_defaults=True, exclude_unset=True) for call in self.calls],
|
136
|
+
}
|
137
|
+
|
138
|
+
|
139
|
+
@lru_cache
|
140
|
+
def _public_private_key() -> tuple[str, str]:
|
141
|
+
public_key = os.environ.get("MONGODB_ATLAS_PUBLIC_KEY")
|
142
|
+
private_key = os.environ.get("MONGODB_ATLAS_PRIVATE_KEY")
|
143
|
+
if not public_key or not private_key:
|
144
|
+
raise ValueError("MONGODB_ATLAS_PUBLIC_KEY and MONGODB_ATLAS_PRIVATE_KEY must be set in environment variables.")
|
145
|
+
return public_key, private_key
|
146
|
+
|
147
|
+
|
148
|
+
class APICallError(Exception):
|
149
|
+
def __init__(self, api_call: ApiCall, json_response: dict, error: requests.exceptions.HTTPError):
|
150
|
+
self.api_call = api_call
|
151
|
+
self.json_response = json_response
|
152
|
+
super().__init__(f"Failed to make API call {api_call}:\njson={json_response}\n{error}")
|
153
|
+
|
154
|
+
@property
|
155
|
+
def error_code(self) -> str:
|
156
|
+
return self.json_response.get("errorCode", "")
|
157
|
+
|
158
|
+
|
159
|
+
def call_api(api_call: ApiCall, path_variables: dict[str, str]) -> dict:
|
160
|
+
resolved_path = api_call.path_with_variables(path_variables)
|
161
|
+
response = requests.get(
|
162
|
+
f"https://cloud-dev.mongodb.com/{resolved_path.lstrip('/')}",
|
163
|
+
params=api_call.query_args,
|
164
|
+
headers={"Accept": api_call.accept_header, "Content-Type": "application/json"},
|
165
|
+
auth=HTTPDigestAuth(*_public_private_key()),
|
166
|
+
timeout=30,
|
167
|
+
)
|
168
|
+
try:
|
169
|
+
response_json = response.json()
|
170
|
+
except requests.exceptions.JSONDecodeError as e:
|
171
|
+
logger.error(f"Failed to parse_json {api_call}: {e}")
|
172
|
+
response_json = {}
|
173
|
+
try:
|
174
|
+
response.raise_for_status()
|
175
|
+
except requests.exceptions.HTTPError as e:
|
176
|
+
raise APICallError(api_call, response_json, e) from e
|
177
|
+
return response_json
|
178
|
+
|
179
|
+
|
180
|
+
class NoSelfLinkError(Exception):
|
181
|
+
def __init__(self, json_response: dict) -> None:
|
182
|
+
self.json_response = json_response
|
183
|
+
super().__init__("No self link found in response")
|
184
|
+
|
185
|
+
|
186
|
+
def parse_href_response(json_response: dict) -> str:
|
187
|
+
for ref in json_response.get("links", []):
|
188
|
+
if ref.get("rel") == "self":
|
189
|
+
return ref.get("href")
|
190
|
+
raise NoSelfLinkError(json_response)
|
191
|
+
|
192
|
+
|
193
|
+
def api_config(
|
194
|
+
config_path_str: str = typer.Option("", "-p", "--path", help="Path to the API config file"),
|
195
|
+
query_args_str: str = typer.Option(
|
196
|
+
'{"pageNum": "0", "itemsPerPage": "0"}', "-q", "--query-args", help="Query arguments for the API call"
|
197
|
+
),
|
198
|
+
verbose: bool = typer.Option(False, "-v", "--verbose", help="Verbose output"),
|
199
|
+
):
|
200
|
+
query_args: dict[str, str] = json.loads(query_args_str)
|
201
|
+
if config_path_str == "":
|
202
|
+
with new_task("Find API Calls that use pagination"):
|
203
|
+
config_path = dump_config_path(query_args)
|
204
|
+
else:
|
205
|
+
config_path = Path(config_path_str)
|
206
|
+
assert config_path.exists(), f"Config file {config_path} does not exist."
|
207
|
+
model = parse_model(config_path, t=ApiCalls)
|
208
|
+
total_calls = len(model.calls)
|
209
|
+
assert _public_private_key(), "Public and private keys must be set in environment variables."
|
210
|
+
path_variables = model.path_variables
|
211
|
+
op_id_path_self_qstring: dict[tuple[str, str], str] = {}
|
212
|
+
with run_pool(
|
213
|
+
task_name="make API calls", max_concurrent_submits=10, threads_used_per_submit=1, total=total_calls
|
214
|
+
) as pool:
|
215
|
+
futures: dict[Future, ApiCall] = {
|
216
|
+
pool.submit(call_api, api_call, path_variables): api_call for api_call in model.calls
|
217
|
+
}
|
218
|
+
for future in as_completed(futures):
|
219
|
+
api_call = futures[future]
|
220
|
+
try:
|
221
|
+
result = future.result()
|
222
|
+
except APICallError as e:
|
223
|
+
if e.error_code in ALLOWED_ERROR_CODES:
|
224
|
+
logger.info(f"Allowed error code {e.error_code} in response for {api_call}")
|
225
|
+
model.ignored_calls.append(api_call)
|
226
|
+
continue
|
227
|
+
raise
|
228
|
+
except Exception as e:
|
229
|
+
logger.error(e)
|
230
|
+
continue
|
231
|
+
try:
|
232
|
+
href = parse_href_response(result)
|
233
|
+
op_id_path_self_qstring[(api_call.operation_id, api_call.path)] = href.split("?")[-1]
|
234
|
+
except NoSelfLinkError as e:
|
235
|
+
logger.error(f"{api_call} did not have a self link in the response:\n{e.json_response}")
|
236
|
+
continue
|
237
|
+
logger.info(f"API call {api_call} completed successfully with self ref:\n{href}")
|
238
|
+
if verbose:
|
239
|
+
logger.info(f"Response for {api_call.query_args} was:\n{dump(result, 'pretty_json')}")
|
240
|
+
query_args_str = "&".join(f"{key}={value}" for key, value in query_args.items())
|
241
|
+
md_report: list[str] = [
|
242
|
+
f"# Pagination Report for query_args='{query_args_str}'",
|
243
|
+
"",
|
244
|
+
"## Checked endpoints",
|
245
|
+
"",
|
246
|
+
"Operation ID | Path | SelfQueryString",
|
247
|
+
"--- | --- | ---",
|
248
|
+
*[
|
249
|
+
f"{operation_id} | {path} | {self_query_string}"
|
250
|
+
for (operation_id, path), self_query_string in op_id_path_self_qstring.items()
|
251
|
+
],
|
252
|
+
"",
|
253
|
+
"## Ignored endpoints (not checked)",
|
254
|
+
"",
|
255
|
+
"Operation ID | Path",
|
256
|
+
"--- | ---",
|
257
|
+
*[f"{call.operation_id} | {call.path}" for call in model.ignored_calls],
|
258
|
+
]
|
259
|
+
md_content = "\n".join(md_report)
|
260
|
+
md = Markdown(md_content)
|
261
|
+
print_to_live(md)
|
262
|
+
output_path = TfExtSettings.from_env().pagination_output_path(query_args_str)
|
263
|
+
ensure_parents_write_text(output_path, md_content)
|
264
|
+
logger.info(f"Pagination report saved to {output_path}")
|
265
|
+
return md
|
266
|
+
|
267
|
+
|
268
|
+
def api(
|
269
|
+
path: str = typer.Option("-p", "--path", help="Path to the API endpoint"),
|
270
|
+
query_string: str = typer.Option("", "-q", "--query-string", help="Query string for the API call"),
|
271
|
+
):
|
272
|
+
assert path, "Path must be provided."
|
273
|
+
accept_header = "application/vnd.atlas.2023-01-01+json"
|
274
|
+
url = f"https://cloud-dev.mongodb.com/{path.lstrip('/')}?{query_string}"
|
275
|
+
logger.info(f"Calling {url}")
|
276
|
+
try:
|
277
|
+
r = requests.get(
|
278
|
+
url,
|
279
|
+
headers={"Accept": accept_header, "Content-Type": "application/json"},
|
280
|
+
auth=HTTPDigestAuth(*_public_private_key()),
|
281
|
+
timeout=30,
|
282
|
+
)
|
283
|
+
print(r.text)
|
284
|
+
r.raise_for_status()
|
285
|
+
except requests.exceptions.HTTPError as e:
|
286
|
+
print(e)
|
287
|
+
print(e.response)
|
288
|
+
|
289
|
+
|
290
|
+
def dump_config_path(query_args: dict[str, str]) -> Path:
|
291
|
+
settings = TfExtSettings.from_env()
|
292
|
+
latest_api_spec = resolve_admin_api_path()
|
293
|
+
model = parse_model(latest_api_spec, t=OpenapiSchema)
|
294
|
+
paginated_paths: list[ApiCall] = []
|
295
|
+
path_versions = list(model.path_method_api_versions())
|
296
|
+
|
297
|
+
for (path, method, code), versions in path_versions:
|
298
|
+
if method != "get" or code != "200":
|
299
|
+
continue
|
300
|
+
assert len(versions) == 1, f"{path} {method} {code} has multiple versions: {versions}"
|
301
|
+
get_method = model.get_method(path)
|
302
|
+
if not get_method:
|
303
|
+
continue
|
304
|
+
parameters = get_method.get("parameters", [])
|
305
|
+
for param in parameters:
|
306
|
+
if param_ref := param.get("$ref"):
|
307
|
+
if param_ref.endswith("itemsPerPage"):
|
308
|
+
version = versions[0].strftime("%Y-%m-%d")
|
309
|
+
paginated_paths.append(
|
310
|
+
ApiCall(
|
311
|
+
path=path,
|
312
|
+
query_args=query_args,
|
313
|
+
accept_header=f"application/vnd.atlas.{version}+json",
|
314
|
+
operation_id=get_method["operationId"],
|
315
|
+
)
|
316
|
+
)
|
317
|
+
config_path = settings.api_calls_path
|
318
|
+
calls = ApiCalls(
|
319
|
+
calls=paginated_paths,
|
320
|
+
skip_validation=True,
|
321
|
+
)
|
322
|
+
calls_yaml = dump(calls.dump_to_dict(), "yaml")
|
323
|
+
logger.info(f"Dumped {len(paginated_paths)} API calls to {config_path}")
|
324
|
+
ensure_parents_write_text(config_path, calls_yaml)
|
325
|
+
return config_path
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import typer
|
2
|
+
|
3
|
+
|
4
|
+
def default_skippped_directories() -> list[str]:
|
5
|
+
return [
|
6
|
+
"prometheus-and-teams", # Provider registry.terraform.io/hashicorp/template v2.2.0 does not have a package available for your current platform, darwin_arm64.
|
7
|
+
]
|
8
|
+
|
9
|
+
|
10
|
+
REPO_PATH_ATLAS_ARG = typer.Argument(..., help="Path to the mongodbatlas-terraform-provider repository")
|
11
|
+
SKIP_EXAMPLES_DIRS_OPTION = typer.Option(
|
12
|
+
...,
|
13
|
+
"--skip-examples",
|
14
|
+
help="Skip example directories with these names",
|
15
|
+
default_factory=default_skippped_directories,
|
16
|
+
show_default=True,
|
17
|
+
)
|
18
|
+
TF_CLI_CONFIG_FILE_ENV_NAME = "TF_CLI_CONFIG_FILE"
|
19
|
+
TF_CLI_CONFIG_FILE_ARG = typer.Option(
|
20
|
+
"",
|
21
|
+
"-tf-cli",
|
22
|
+
"--tf-cli-config-file",
|
23
|
+
envvar=TF_CLI_CONFIG_FILE_ENV_NAME,
|
24
|
+
help="Terraform CLI config file",
|
25
|
+
)
|
26
|
+
ENV_NAME_REPO_PATH_ATLAS_PROVIDER = "REPO_PATH_ATLAS_PROVIDER"
|
27
|
+
TF_REPO_PATH_ATLAS = typer.Option(
|
28
|
+
"",
|
29
|
+
"--tf-repo-path-atlas",
|
30
|
+
help="Path to the mongodbatlas-terraform-provider repository",
|
31
|
+
envvar=ENV_NAME_REPO_PATH_ATLAS_PROVIDER,
|
32
|
+
)
|