atlas-init 0.3.0__py3-none-any.whl → 0.3.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.
- atlas_init/__init__.py +1 -1
- atlas_init/cli_tf/app.py +1 -1
- atlas_init/cli_tf/debug_logs.py +6 -6
- atlas_init/cli_tf/debug_logs_test_data.py +7 -14
- atlas_init/cli_tf/mock_tf_log.py +101 -16
- atlas_init/cli_tf/schema.py +117 -48
- atlas_init/repos/go_sdk.py +24 -2
- atlas_init/settings/path.py +1 -0
- atlas_init/settings/rich_utils.py +1 -1
- atlas_init/typer_app.py +5 -1
- {atlas_init-0.3.0.dist-info → atlas_init-0.3.2.dist-info}/METADATA +1 -1
- {atlas_init-0.3.0.dist-info → atlas_init-0.3.2.dist-info}/RECORD +14 -14
- {atlas_init-0.3.0.dist-info → atlas_init-0.3.2.dist-info}/WHEEL +0 -0
- {atlas_init-0.3.0.dist-info → atlas_init-0.3.2.dist-info}/entry_points.txt +0 -0
atlas_init/__init__.py
CHANGED
atlas_init/cli_tf/app.py
CHANGED
@@ -30,7 +30,6 @@ from atlas_init.cli_tf.go_test_summary import (
|
|
30
30
|
)
|
31
31
|
from atlas_init.cli_tf.mock_tf_log import mock_tf_log_cmd
|
32
32
|
from atlas_init.cli_tf.schema import (
|
33
|
-
download_admin_api,
|
34
33
|
dump_generator_config,
|
35
34
|
parse_py_terraform_schema,
|
36
35
|
update_provider_code_spec,
|
@@ -42,6 +41,7 @@ from atlas_init.cli_tf.schema_v2 import (
|
|
42
41
|
)
|
43
42
|
from atlas_init.cli_tf.schema_v2_api_parsing import add_api_spec_info
|
44
43
|
from atlas_init.cli_tf.schema_v2_sdk import generate_model_go, parse_sdk_model
|
44
|
+
from atlas_init.repos.go_sdk import download_admin_api
|
45
45
|
from atlas_init.repos.path import Repo, current_repo_path
|
46
46
|
from atlas_init.settings.env_vars import init_settings
|
47
47
|
from atlas_init.settings.interactive import confirm
|
atlas_init/cli_tf/debug_logs.py
CHANGED
@@ -98,12 +98,12 @@ class SDKRoundtrip(Entity):
|
|
98
98
|
@property
|
99
99
|
def version(self) -> str:
|
100
100
|
content_type = self.response.headers.get("Content-Type", "v1")
|
101
|
-
|
101
|
+
content_type_req = self.request.headers.get("Accept", "v1")
|
102
|
+
with suppress(ValueError):
|
102
103
|
return extract_version(content_type)
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
return extract_version(content_type)
|
104
|
+
with suppress(ValueError):
|
105
|
+
return extract_version(content_type_req)
|
106
|
+
raise ValueError(f"Could not extract version from req/resp: {content_type} or {content_type_req}")
|
107
107
|
|
108
108
|
@model_validator(mode="after")
|
109
109
|
def ensure_match(self) -> Self:
|
@@ -159,7 +159,7 @@ def parse_http_requests(logs: str) -> list[SDKRoundtrip]:
|
|
159
159
|
Can say that expected payload is either a list or a dict and if it ends with an identifier it is higher chance for a dict
|
160
160
|
"""
|
161
161
|
test_name = parse_test_name(logs)
|
162
|
-
logger.info(f"Finding http requests for test name: {test_name}")
|
162
|
+
logger.info(f"Finding http requests for test name: '{test_name}'")
|
163
163
|
requests, responses = parse_raw_req_responses(logs)
|
164
164
|
tf_step_starts = [i for i, line in enumerate(logs.splitlines()) if MARKER_START_STEP in line]
|
165
165
|
used_responses: set[int] = set()
|
@@ -31,14 +31,7 @@ class RequestInfo(Entity):
|
|
31
31
|
|
32
32
|
@property
|
33
33
|
def id(self):
|
34
|
-
return "__".join(
|
35
|
-
[
|
36
|
-
self.method,
|
37
|
-
self.path,
|
38
|
-
self.version,
|
39
|
-
self.text,
|
40
|
-
] # need to include text to differentiate between requests
|
41
|
-
)
|
34
|
+
return "__".join(part for part in (self.method, self.path, self.version, self.text) if part)
|
42
35
|
|
43
36
|
|
44
37
|
class StepRequests(Entity):
|
@@ -150,9 +143,9 @@ class MockRequestData(Entity):
|
|
150
143
|
def replace_text_variables(self):
|
151
144
|
for step in self.steps:
|
152
145
|
for request in step.all_requests:
|
153
|
-
request.text = normalize_text(request.text, self.variables)
|
146
|
+
request.text = normalize_text(request.text, self.variables, expect_json=True)
|
154
147
|
for response in request.responses:
|
155
|
-
response.text = normalize_text(response.text, self.variables)
|
148
|
+
response.text = normalize_text(response.text, self.variables, expect_json=True)
|
156
149
|
|
157
150
|
def prune_duplicate_responses(self):
|
158
151
|
for step in self.steps:
|
@@ -209,10 +202,10 @@ def find_normalized_path(path: str, api_spec_paths: list[ApiSpecPath]) -> ApiSpe
|
|
209
202
|
raise ValueError(f"Could not find path: {path}")
|
210
203
|
|
211
204
|
|
212
|
-
def normalize_text(text: str, variables: dict[str, str]) -> str:
|
205
|
+
def normalize_text(text: str, variables: dict[str, str], *, expect_json: bool = False) -> str:
|
213
206
|
for var, value in variables.items():
|
214
207
|
text = text.replace(value, f"{{{var}}}")
|
215
|
-
if not text:
|
208
|
+
if not text or not expect_json:
|
216
209
|
return text
|
217
210
|
try:
|
218
211
|
parsed_text = json.loads(text)
|
@@ -267,8 +260,8 @@ def create_mock_data(
|
|
267
260
|
for modifier in modifiers:
|
268
261
|
if modifier.match(rt, normalized_path):
|
269
262
|
modifier.modification(rt)
|
270
|
-
normalized_text = normalize_text(rt.request.text, mock_data.variables)
|
271
|
-
normalized_response_text = normalize_text(rt.response.text, mock_data.variables)
|
263
|
+
normalized_text = normalize_text(rt.request.text, mock_data.variables, expect_json=True)
|
264
|
+
normalized_response_text = normalize_text(rt.response.text, mock_data.variables, expect_json=True)
|
272
265
|
mock_data.add_roundtrip(rt, normalized_path, normalized_text, normalized_response_text, is_diff(rt))
|
273
266
|
mock_data.replace_text_variables()
|
274
267
|
if prune_duplicates:
|
atlas_init/cli_tf/mock_tf_log.py
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
import json
|
2
2
|
import logging
|
3
|
+
import time
|
4
|
+
from collections.abc import Callable
|
5
|
+
from io import StringIO
|
3
6
|
from pathlib import Path
|
4
7
|
from typing import Self
|
5
8
|
|
6
9
|
import typer
|
7
|
-
|
10
|
+
import yaml
|
11
|
+
from model_lib import Entity
|
8
12
|
from pydantic import Field, model_validator
|
9
13
|
from zero_3rdparty import file_utils
|
10
14
|
|
@@ -14,8 +18,17 @@ from atlas_init.cli_tf.debug_logs import (
|
|
14
18
|
parse_http_requests,
|
15
19
|
parse_test_name,
|
16
20
|
)
|
17
|
-
from atlas_init.cli_tf.debug_logs_test_data import
|
18
|
-
|
21
|
+
from atlas_init.cli_tf.debug_logs_test_data import (
|
22
|
+
RTModifier,
|
23
|
+
create_mock_data,
|
24
|
+
default_is_diff,
|
25
|
+
)
|
26
|
+
from atlas_init.repos.go_sdk import (
|
27
|
+
api_spec_path_transformed,
|
28
|
+
download_admin_api,
|
29
|
+
parse_api_spec_paths,
|
30
|
+
)
|
31
|
+
from atlas_init.settings.path import DEFAULT_DOWNLOADS_DIR
|
19
32
|
|
20
33
|
logger = logging.getLogger(__name__)
|
21
34
|
|
@@ -23,18 +36,20 @@ logger = logging.getLogger(__name__)
|
|
23
36
|
class MockTFLog(Entity):
|
24
37
|
log_path: Path
|
25
38
|
output_dir: Path
|
26
|
-
|
39
|
+
admin_api_path: Path
|
27
40
|
diff_skip_suffixes: list[str] = Field(default_factory=list)
|
28
41
|
keep_duplicates: bool = False
|
42
|
+
modifiers: list[RTModifier] = Field(default_factory=list)
|
43
|
+
log_diff_roundtrips: bool = False
|
29
44
|
|
30
45
|
@model_validator(mode="after")
|
31
46
|
def ensure_paths_exist(self) -> Self:
|
32
47
|
if not self.log_path.exists():
|
33
|
-
raise ValueError(f"log_path: {self.log_path} doesn't exist")
|
34
|
-
if not self.
|
35
|
-
raise ValueError(f"
|
48
|
+
raise ValueError(f"log_path: '{self.log_path}' doesn't exist")
|
49
|
+
if not self.admin_api_path.exists():
|
50
|
+
raise ValueError(f"admin_api_path: '{self.admin_api_path}' doesn't exist")
|
36
51
|
if not self.output_dir.exists():
|
37
|
-
raise ValueError(f"output_dir: {self.output_dir} doesn't exist")
|
52
|
+
raise ValueError(f"output_dir: '{self.output_dir}' doesn't exist")
|
38
53
|
assert self.output_dir.name == "testdata", "output_path should be a directory named testdata"
|
39
54
|
return self
|
40
55
|
|
@@ -42,44 +57,114 @@ class MockTFLog(Entity):
|
|
42
57
|
return default_is_diff(rt) and not any(rt.request.path.endswith(suffix) for suffix in self.diff_skip_suffixes)
|
43
58
|
|
44
59
|
|
45
|
-
def mock_tf_log(req: MockTFLog) ->
|
60
|
+
def mock_tf_log(req: MockTFLog) -> Path:
|
46
61
|
log_file_text = req.log_path.read_text()
|
47
62
|
test_name = parse_test_name(log_file_text)
|
48
63
|
roundtrips = parse_http_requests(log_file_text)
|
49
|
-
|
64
|
+
logger.info(f"Found #{len(roundtrips)} roundtrips")
|
65
|
+
if req.log_diff_roundtrips:
|
66
|
+
log_diff_roundtrips(roundtrips, req.differ)
|
67
|
+
api_spec_paths = parse_api_spec_paths(req.admin_api_path)
|
50
68
|
data = create_mock_data(
|
51
69
|
roundtrips,
|
52
70
|
api_spec_paths,
|
53
71
|
is_diff=req.differ,
|
54
72
|
prune_duplicates=not req.keep_duplicates,
|
73
|
+
modifiers=req.modifiers,
|
55
74
|
)
|
56
75
|
# avoid anchors
|
57
|
-
|
76
|
+
data_json = data.model_dump_json(exclude_none=True)
|
77
|
+
data_parsed = json.loads(data_json)
|
78
|
+
s = StringIO()
|
79
|
+
yaml.safe_dump(
|
80
|
+
data_parsed,
|
81
|
+
s,
|
82
|
+
default_flow_style=False,
|
83
|
+
width=100_000,
|
84
|
+
allow_unicode=True,
|
85
|
+
sort_keys=False,
|
86
|
+
)
|
87
|
+
data_yaml = s.getvalue()
|
88
|
+
test_name = test_name.replace("TestAcc", "TestMock")
|
58
89
|
output_path = req.output_dir / f"{test_name}.yaml"
|
90
|
+
logger.info(f"Variables found {data.variables}")
|
59
91
|
logger.info(f"Writing to {output_path}")
|
60
92
|
file_utils.ensure_parents_write_text(output_path, data_yaml)
|
93
|
+
return output_path
|
61
94
|
|
62
95
|
|
63
96
|
def mock_tf_log_cmd(
|
64
97
|
log_path: str = typer.Argument(..., help="the path to the log file generated with TF_LOG_PATH"),
|
65
|
-
sdk_repo_path_str: str = option_sdk_repo_path,
|
66
98
|
output_testdir: str = typer.Option(
|
67
99
|
"",
|
68
100
|
"-o",
|
69
101
|
"--output-testdir",
|
70
|
-
help="the path to the output test directory, for example: internal/service/advancedclustertpf/testdata/",
|
102
|
+
help="the path to the output test directory, for example: internal/service/advancedclustertpf/testdata/, uses $(cwd)/testdata by default",
|
103
|
+
),
|
104
|
+
sdk_repo_path_str: str = option_sdk_repo_path,
|
105
|
+
sdk_branch: str = typer.Option("main", "-b", "--branch", help="the branch for downloading openapi spec"),
|
106
|
+
admin_api_path: str = typer.Option(
|
107
|
+
"", "-a", "--admin-api-path", help="the path to store/download the openapi spec"
|
71
108
|
),
|
72
109
|
diff_skip_suffixes: list[str] = typer.Option(..., "-s", "--skip-suffixes", default_factory=list),
|
73
110
|
keep_duplicates: bool = typer.Option(False, "-keep", "--keep-duplicates", help="keep duplicate requests"),
|
111
|
+
log_diff_roundtrips: bool = typer.Option(
|
112
|
+
False, "-l", "--log-diff-roundtrips", help="print out the roundtrips used in diffs"
|
113
|
+
),
|
74
114
|
):
|
75
115
|
cwd = Path.cwd()
|
76
|
-
|
77
|
-
|
116
|
+
default_testdir = cwd / "testdata"
|
117
|
+
resolved_admin_api_path = resolve_admin_api_path(sdk_repo_path_str, sdk_branch, admin_api_path)
|
78
118
|
event_in = MockTFLog(
|
79
119
|
log_path=Path(log_path),
|
80
120
|
output_dir=Path(output_testdir) if output_testdir else default_testdir,
|
81
|
-
|
121
|
+
admin_api_path=resolved_admin_api_path,
|
82
122
|
diff_skip_suffixes=diff_skip_suffixes,
|
83
123
|
keep_duplicates=keep_duplicates,
|
124
|
+
log_diff_roundtrips=log_diff_roundtrips,
|
84
125
|
)
|
85
126
|
mock_tf_log(event_in)
|
127
|
+
|
128
|
+
|
129
|
+
def is_cache_up_to_date(cache_path: Path, cache_ttl: int) -> bool:
|
130
|
+
if cache_path.exists():
|
131
|
+
modified_ts = file_utils.file_modified_time(cache_path)
|
132
|
+
if modified_ts > time.time() - cache_ttl:
|
133
|
+
logger.info(f"using cached admin api: {cache_path} downloaded {time.time()-modified_ts:.0f}s ago")
|
134
|
+
return True
|
135
|
+
return False
|
136
|
+
|
137
|
+
|
138
|
+
def resolve_admin_api_path(sdk_repo_path_str: str, sdk_branch: str, admin_api_path: str) -> Path:
|
139
|
+
if admin_api_path:
|
140
|
+
resolved_admin_api_path = Path(admin_api_path)
|
141
|
+
if not resolved_admin_api_path.exists():
|
142
|
+
download_admin_api(resolved_admin_api_path, sdk_branch)
|
143
|
+
elif sdk_repo_path_str:
|
144
|
+
sdk_repo_path = Path(sdk_repo_path_str)
|
145
|
+
assert sdk_repo_path.exists(), f"not found sdk_repo_path={sdk_repo_path}"
|
146
|
+
resolved_admin_api_path = api_spec_path_transformed(sdk_repo_path)
|
147
|
+
else:
|
148
|
+
resolved_admin_api_path = DEFAULT_DOWNLOADS_DIR / "atlas-api-transformed.yaml"
|
149
|
+
if not is_cache_up_to_date(resolved_admin_api_path, 3600):
|
150
|
+
download_admin_api(resolved_admin_api_path, sdk_branch)
|
151
|
+
assert resolved_admin_api_path.exists(), f"unable to resolve admin_api_path={resolved_admin_api_path}"
|
152
|
+
assert resolved_admin_api_path.is_file(), f"not a file admin_api_path={resolved_admin_api_path}"
|
153
|
+
return resolved_admin_api_path
|
154
|
+
|
155
|
+
|
156
|
+
def log_diff_roundtrips(roundtrips: list[SDKRoundtrip], differ: Callable[[SDKRoundtrip], bool] | None = None):
|
157
|
+
differ = differ or default_is_diff
|
158
|
+
diff_count = 0
|
159
|
+
step_nr = 0
|
160
|
+
for rt in roundtrips:
|
161
|
+
if not differ(rt):
|
162
|
+
continue
|
163
|
+
if rt.step_number != step_nr:
|
164
|
+
logger.info(f"{'-' * 80}\nStep {rt.step_number}")
|
165
|
+
step_nr = rt.step_number
|
166
|
+
diff_count += 1
|
167
|
+
logger.info(
|
168
|
+
f"\n{rt.request.method} {rt.request.path}\n{rt.request.text}\n{rt.response.status}-{rt.response.status_text}\n{rt.response.text}"
|
169
|
+
)
|
170
|
+
logger.info(f"Diffable requests: {diff_count}")
|
atlas_init/cli_tf/schema.py
CHANGED
@@ -5,7 +5,6 @@ from pathlib import Path
|
|
5
5
|
from typing import Annotated, Literal, NamedTuple
|
6
6
|
|
7
7
|
import pydantic
|
8
|
-
import requests
|
9
8
|
from model_lib import Entity, dump, field_names, parse_model
|
10
9
|
from zero_3rdparty import dict_nested
|
11
10
|
from zero_3rdparty.enum_utils import StrEnum
|
@@ -66,14 +65,19 @@ class SkipValidators(Entity):
|
|
66
65
|
type: Literal["skip_validators"] = "skip_validators"
|
67
66
|
|
68
67
|
|
69
|
-
Extension = Annotated[
|
68
|
+
Extension = Annotated[
|
69
|
+
IgnoreNested | RenameAttribute | ChangeAttributeType | SkipValidators,
|
70
|
+
pydantic.Field("type"),
|
71
|
+
]
|
70
72
|
|
71
73
|
|
72
74
|
class TFResource(Entity):
|
73
75
|
model_config = pydantic.ConfigDict(extra="allow")
|
74
76
|
name: str
|
75
77
|
extensions: list[Extension] = pydantic.Field(default_factory=list)
|
76
|
-
provider_spec_attributes: list[ProviderSpecAttribute] = pydantic.Field(
|
78
|
+
provider_spec_attributes: list[ProviderSpecAttribute] = pydantic.Field(
|
79
|
+
default_factory=list
|
80
|
+
)
|
77
81
|
|
78
82
|
def dump_generator_config(self) -> dict:
|
79
83
|
names = field_names(self)
|
@@ -129,7 +133,9 @@ class ProviderCodeSpec(Entity):
|
|
129
133
|
raise ValueError(f"{self.root_name(name, is_datasource)} not found!")
|
130
134
|
return root_value
|
131
135
|
|
132
|
-
def schema_attributes(
|
136
|
+
def schema_attributes(
|
137
|
+
self, name: str, is_datasource: bool = False
|
138
|
+
) -> list:
|
133
139
|
root_dict = self.root_dict(name, is_datasource)
|
134
140
|
return root_dict["schema"]["attributes"]
|
135
141
|
|
@@ -139,16 +145,26 @@ class ProviderCodeSpec(Entity):
|
|
139
145
|
def root_name(self, name: str, is_datasource: bool):
|
140
146
|
return f"{self._type_name(is_datasource)}.{name}"
|
141
147
|
|
142
|
-
def attribute_names(
|
143
|
-
|
148
|
+
def attribute_names(
|
149
|
+
self, name: str, is_datasource: bool = False
|
150
|
+
) -> list[str]:
|
151
|
+
return [
|
152
|
+
a["name"] for a in self.schema_attributes(name, is_datasource=is_datasource)
|
153
|
+
]
|
144
154
|
|
145
|
-
def iter_all_attributes(
|
155
|
+
def iter_all_attributes(
|
156
|
+
self, name: str, is_datasource: bool = False
|
157
|
+
) -> Iterable[AttributeTuple]:
|
146
158
|
for attribute in self.schema_attributes(name=name, is_datasource=is_datasource):
|
147
159
|
yield AttributeTuple(attribute["name"], "", attribute)
|
148
160
|
yield from self.iter_nested_attributes(name, is_datasource=is_datasource)
|
149
161
|
|
150
|
-
def iter_nested_attributes(
|
151
|
-
|
162
|
+
def iter_nested_attributes(
|
163
|
+
self, name: str, is_datasource: bool = False
|
164
|
+
) -> Iterable[AttributeTuple]:
|
165
|
+
for i, attribute in enumerate(
|
166
|
+
self.schema_attributes(name=name, is_datasource=is_datasource)
|
167
|
+
):
|
152
168
|
for path, attr_dict in dict_nested.iter_nested_key_values(
|
153
169
|
attribute, type_filter=dict, include_list_indexes=True
|
154
170
|
):
|
@@ -156,32 +172,53 @@ class ProviderCodeSpec(Entity):
|
|
156
172
|
if name := attr_dict.get("name", ""):
|
157
173
|
yield AttributeTuple(name, full_path, attr_dict)
|
158
174
|
|
159
|
-
def remove_nested_attribute(
|
175
|
+
def remove_nested_attribute(
|
176
|
+
self, name: str, path: str, is_datasource: bool = False
|
177
|
+
) -> None:
|
160
178
|
root_name = self.root_name(name, is_datasource)
|
161
179
|
logger.info(f"will remove attribute from {root_name} with path: {path}")
|
162
180
|
root_attributes = self.root_dict(name, is_datasource)
|
163
181
|
full_path = f"schema.attributes.{path}"
|
164
182
|
popped = dict_nested.pop_nested(root_attributes, full_path, "")
|
165
183
|
if popped == "":
|
166
|
-
raise ValueError(
|
167
|
-
|
184
|
+
raise ValueError(
|
185
|
+
f"failed to remove attribute from resource {name} with path: {full_path}"
|
186
|
+
)
|
187
|
+
assert isinstance(
|
188
|
+
popped, dict
|
189
|
+
), f"expected removed attribute to be a dict, got: {popped}"
|
168
190
|
logger.info(f"removal ok, attribute_name: '{root_name}.{popped.get('name')}'")
|
169
191
|
|
170
|
-
def read_attribute(
|
192
|
+
def read_attribute(
|
193
|
+
self, name: str, path: str, *, is_datasource: bool = False
|
194
|
+
) -> dict:
|
171
195
|
if "." not in path:
|
172
|
-
attribute_dict = next(
|
196
|
+
attribute_dict = next(
|
197
|
+
(
|
198
|
+
a
|
199
|
+
for a in self.schema_attributes(name, is_datasource)
|
200
|
+
if a["name"] == path
|
201
|
+
),
|
202
|
+
None,
|
203
|
+
)
|
173
204
|
else:
|
174
205
|
root_dict = self.root_dict(name, is_datasource)
|
175
|
-
attribute_dict = dict_nested.read_nested_or_none(
|
206
|
+
attribute_dict = dict_nested.read_nested_or_none(
|
207
|
+
root_dict, f"schema.attributes.{path}"
|
208
|
+
)
|
176
209
|
if attribute_dict is None:
|
177
|
-
raise ValueError(
|
210
|
+
raise ValueError(
|
211
|
+
f"attribute {path} not found in {self.root_name(name, is_datasource)}"
|
212
|
+
)
|
178
213
|
assert isinstance(
|
179
214
|
attribute_dict, dict
|
180
215
|
), f"expected attribute to be a dict, got: {attribute_dict} @ {path} for resource={name}"
|
181
216
|
return attribute_dict
|
182
217
|
|
183
218
|
|
184
|
-
def update_provider_code_spec(
|
219
|
+
def update_provider_code_spec(
|
220
|
+
schema: PyTerraformSchema, provider_code_spec_path: Path
|
221
|
+
) -> str:
|
185
222
|
spec = parse_model(provider_code_spec_path, t=ProviderCodeSpec)
|
186
223
|
for resource in schema.resources:
|
187
224
|
resource_name = resource.name
|
@@ -192,41 +229,67 @@ def update_provider_code_spec(schema: PyTerraformSchema, provider_code_spec_path
|
|
192
229
|
for data_source in schema.data_sources:
|
193
230
|
data_source_name = data_source.name
|
194
231
|
if extra_spec_attributes := data_source.provider_spec_attributes:
|
195
|
-
add_explicit_attributes(
|
232
|
+
add_explicit_attributes(
|
233
|
+
spec, data_source_name, extra_spec_attributes, is_datasource=True
|
234
|
+
)
|
196
235
|
for extension in data_source.extensions:
|
197
236
|
apply_extension(extension, spec, data_source_name, is_datasource=True)
|
198
237
|
return dump(spec, "json")
|
199
238
|
|
200
239
|
|
201
240
|
def add_explicit_attributes(
|
202
|
-
spec: ProviderCodeSpec,
|
241
|
+
spec: ProviderCodeSpec,
|
242
|
+
name: str,
|
243
|
+
extra_spec_attributes: list[ProviderSpecAttribute],
|
244
|
+
*,
|
245
|
+
is_datasource=False,
|
203
246
|
):
|
204
247
|
resource_attributes = spec.schema_attributes(name, is_datasource=is_datasource)
|
205
248
|
existing_names = spec.attribute_names(name, is_datasource=is_datasource)
|
206
249
|
new_names = [extra.name for extra in extra_spec_attributes]
|
207
250
|
if both := set(existing_names) & set(new_names):
|
208
251
|
raise ValueError(f"resource: {name}, has already: {both} attributes")
|
209
|
-
resource_attributes.extend(
|
252
|
+
resource_attributes.extend(
|
253
|
+
extra.dump_provider_code_spec() for extra in extra_spec_attributes
|
254
|
+
)
|
210
255
|
|
211
256
|
|
212
257
|
@singledispatch
|
213
|
-
def apply_extension(
|
258
|
+
def apply_extension(
|
259
|
+
extension: object,
|
260
|
+
spec: ProviderCodeSpec,
|
261
|
+
resource_name: str,
|
262
|
+
*,
|
263
|
+
is_datasource: bool = False,
|
264
|
+
):
|
214
265
|
raise NotImplementedError(f"unsupported extension: {extension!r}")
|
215
266
|
|
216
267
|
|
217
268
|
@apply_extension.register # type: ignore
|
218
|
-
def _ignore_nested(
|
269
|
+
def _ignore_nested(
|
270
|
+
extension: IgnoreNested,
|
271
|
+
spec: ProviderCodeSpec,
|
272
|
+
resource_name: str,
|
273
|
+
*,
|
274
|
+
is_datasource: bool = False,
|
275
|
+
):
|
219
276
|
if extension.use_wildcard:
|
220
277
|
name_to_remove = extension.path.removeprefix("*.")
|
221
|
-
assert
|
278
|
+
assert (
|
279
|
+
"*" not in name_to_remove
|
280
|
+
), f"only prefix *. is allowed for wildcard in path {extension.path}"
|
222
281
|
found_paths = [
|
223
282
|
path
|
224
|
-
for name, path, attribute_dict in spec.iter_nested_attributes(
|
283
|
+
for name, path, attribute_dict in spec.iter_nested_attributes(
|
284
|
+
resource_name, is_datasource=is_datasource
|
285
|
+
)
|
225
286
|
if name == name_to_remove
|
226
287
|
]
|
227
288
|
while found_paths:
|
228
289
|
next_to_remove = found_paths.pop()
|
229
|
-
spec.remove_nested_attribute(
|
290
|
+
spec.remove_nested_attribute(
|
291
|
+
resource_name, next_to_remove, is_datasource=is_datasource
|
292
|
+
)
|
230
293
|
found_paths = [
|
231
294
|
path
|
232
295
|
for name, path, attribute_dict in spec.iter_nested_attributes(
|
@@ -241,9 +304,15 @@ def _ignore_nested(extension: IgnoreNested, spec: ProviderCodeSpec, resource_nam
|
|
241
304
|
|
242
305
|
@apply_extension.register # type: ignore
|
243
306
|
def _rename_attribute(
|
244
|
-
extension: RenameAttribute,
|
307
|
+
extension: RenameAttribute,
|
308
|
+
spec: ProviderCodeSpec,
|
309
|
+
resource_name: str,
|
310
|
+
*,
|
311
|
+
is_datasource: bool = False,
|
245
312
|
):
|
246
|
-
for attribute_dict in spec.schema_attributes(
|
313
|
+
for attribute_dict in spec.schema_attributes(
|
314
|
+
resource_name, is_datasource=is_datasource
|
315
|
+
):
|
247
316
|
if attribute_dict.get("name") == extension.from_name:
|
248
317
|
logger.info(
|
249
318
|
f"renaming attribute for {spec.root_name(resource_name, is_datasource)}: {extension.from_name} -> {extension.to_name}"
|
@@ -253,9 +322,15 @@ def _rename_attribute(
|
|
253
322
|
|
254
323
|
@apply_extension.register # type: ignore
|
255
324
|
def _change_attribute_type(
|
256
|
-
extension: ChangeAttributeType,
|
325
|
+
extension: ChangeAttributeType,
|
326
|
+
spec: ProviderCodeSpec,
|
327
|
+
resource_name: str,
|
328
|
+
*,
|
329
|
+
is_datasource: bool = False,
|
257
330
|
):
|
258
|
-
attribute_dict = spec.read_attribute(
|
331
|
+
attribute_dict = spec.read_attribute(
|
332
|
+
resource_name, extension.path, is_datasource=is_datasource
|
333
|
+
)
|
259
334
|
old_value = extension.read_value(attribute_dict)
|
260
335
|
if old_value == extension.new_value:
|
261
336
|
logger.info(
|
@@ -270,31 +345,25 @@ def _change_attribute_type(
|
|
270
345
|
|
271
346
|
|
272
347
|
@apply_extension.register # type: ignore
|
273
|
-
def _skip_validators(
|
274
|
-
|
348
|
+
def _skip_validators(
|
349
|
+
_: SkipValidators,
|
350
|
+
spec: ProviderCodeSpec,
|
351
|
+
resource_name: str,
|
352
|
+
*,
|
353
|
+
is_datasource: bool = False,
|
354
|
+
):
|
355
|
+
for attr_tuple in spec.iter_all_attributes(
|
356
|
+
resource_name, is_datasource=is_datasource
|
357
|
+
):
|
275
358
|
attribute_dict = attr_tuple.attribute_dict
|
276
359
|
paths_to_pop = [
|
277
360
|
f"{path}.validators"
|
278
|
-
for path, nested_dict in dict_nested.iter_nested_key_values(
|
361
|
+
for path, nested_dict in dict_nested.iter_nested_key_values(
|
362
|
+
attribute_dict, type_filter=dict
|
363
|
+
)
|
279
364
|
if "validators" in nested_dict
|
280
365
|
]
|
281
366
|
if paths_to_pop:
|
282
367
|
logger.info(f"popping validators from '{attr_tuple.attribute_path}'")
|
283
368
|
for path in paths_to_pop:
|
284
369
|
dict_nested.pop_nested(attribute_dict, path)
|
285
|
-
|
286
|
-
|
287
|
-
# reusing url from terraform-provider-mongodbatlas/scripts/schema-scaffold.sh
|
288
|
-
ADMIN_API_URL = "https://raw.githubusercontent.com/mongodb/atlas-sdk-go/main/openapi/atlas-api-transformed.yaml"
|
289
|
-
|
290
|
-
|
291
|
-
def admin_api_url(branch: str) -> str:
|
292
|
-
return ADMIN_API_URL.replace("/main/", f"/{branch}/")
|
293
|
-
|
294
|
-
|
295
|
-
def download_admin_api(dest: Path, branch: str = "main") -> None:
|
296
|
-
url = admin_api_url(branch)
|
297
|
-
logger.info(f"downloading admin api to {dest} from {url}")
|
298
|
-
response = requests.get(url, timeout=10)
|
299
|
-
response.raise_for_status()
|
300
|
-
dest.write_bytes(response.content)
|
atlas_init/repos/go_sdk.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
from collections import defaultdict
|
2
2
|
from pathlib import Path
|
3
3
|
|
4
|
+
import requests
|
4
5
|
from model_lib import parse_model
|
5
6
|
|
6
7
|
from atlas_init.cli_tf.debug_logs_test_data import ApiSpecPath
|
8
|
+
from atlas_init.cli_tf.schema import logger
|
7
9
|
from atlas_init.cli_tf.schema_v2_api_parsing import OpenapiSchema
|
8
10
|
|
9
11
|
|
@@ -15,11 +17,31 @@ def go_sdk_breaking_changes(repo_path: Path, go_sdk_rel_path: str = "../atlas-sd
|
|
15
17
|
return breaking_changes_dir
|
16
18
|
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
+
def api_spec_path_transformed(sdk_repo_path: Path) -> Path:
|
21
|
+
return sdk_repo_path / "openapi/atlas-api-transformed.yaml"
|
22
|
+
|
23
|
+
|
24
|
+
def parse_api_spec_paths(api_spec_path: Path) -> dict[str, list[ApiSpecPath]]:
|
20
25
|
model = parse_model(api_spec_path, t=OpenapiSchema)
|
21
26
|
paths: dict[str, list[ApiSpecPath]] = defaultdict(list)
|
22
27
|
for path, path_dict in model.paths.items():
|
23
28
|
for method in path_dict:
|
24
29
|
paths[method.upper()].append(ApiSpecPath(path=path))
|
25
30
|
return paths
|
31
|
+
|
32
|
+
|
33
|
+
# reusing url from terraform-provider-mongodbatlas/scripts/schema-scaffold.sh
|
34
|
+
ADMIN_API_URL = "https://raw.githubusercontent.com/mongodb/atlas-sdk-go/main/openapi/atlas-api-transformed.yaml"
|
35
|
+
|
36
|
+
|
37
|
+
def admin_api_url(branch: str) -> str:
|
38
|
+
return ADMIN_API_URL.replace("/main/", f"/{branch}/")
|
39
|
+
|
40
|
+
|
41
|
+
def download_admin_api(dest: Path, branch: str = "main") -> None:
|
42
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
43
|
+
url = admin_api_url(branch)
|
44
|
+
logger.info(f"downloading admin api to {dest} from {url}")
|
45
|
+
response = requests.get(url, timeout=10)
|
46
|
+
response.raise_for_status()
|
47
|
+
dest.write_bytes(response.content)
|
atlas_init/settings/path.py
CHANGED
@@ -28,6 +28,7 @@ DEFAULT_CONFIG_PATH = ROOT_PATH / "atlas_init.yaml"
|
|
28
28
|
DEFAULT_SCHEMA_CONFIG_PATH = ROOT_PATH / "terraform.yaml"
|
29
29
|
DEFAULT_GITHUB_CI_RUN_LOGS = ROOT_PATH / "github_ci_run_logs"
|
30
30
|
DEFAULT_GITHUB_SUMMARY_DIR = ROOT_PATH / "github_ci_summary"
|
31
|
+
DEFAULT_DOWNLOADS_DIR = ROOT_PATH / "downloads"
|
31
32
|
|
32
33
|
|
33
34
|
def load_dotenv(env_path: Path) -> dict[str, str]:
|
@@ -46,7 +46,7 @@ def hide_secrets(handler: logging.Handler, secrets_dict: dict[str, str]) -> None
|
|
46
46
|
|
47
47
|
def configure_logging(log_level: str = "INFO") -> logging.Handler:
|
48
48
|
_LogLevel(log_level=log_level) # type: ignore
|
49
|
-
handler = RichHandler(rich_tracebacks=
|
49
|
+
handler = RichHandler(rich_tracebacks=False)
|
50
50
|
logging.basicConfig(
|
51
51
|
level=logging.getLevelName(log_level),
|
52
52
|
format="%(message)s",
|
atlas_init/typer_app.py
CHANGED
@@ -59,4 +59,8 @@ def main(
|
|
59
59
|
logger.info(f"running in repo: {running_in_repo()} python location:{sys.executable}")
|
60
60
|
if not show_secrets:
|
61
61
|
hide_secrets(log_handler, {**os.environ})
|
62
|
-
logger.info(f"in the app callback, log-level: {log_level}, command: {ctx
|
62
|
+
logger.info(f"in the app callback, log-level: {log_level}, command: {format_cmd(ctx)}")
|
63
|
+
|
64
|
+
|
65
|
+
def format_cmd(ctx: typer.Context) -> str:
|
66
|
+
return f"'{ctx.info_name} {ctx.invoked_subcommand}'"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: atlas-init
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.2
|
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
|
@@ -1,11 +1,11 @@
|
|
1
|
-
atlas_init/__init__.py,sha256=
|
1
|
+
atlas_init/__init__.py,sha256=5-YoPkhK1DsNmcQTFT5lp8zxJ26l6WRY5tMcoGBug6o,372
|
2
2
|
atlas_init/__main__.py,sha256=dY1dWWvwxRZMmnOFla6RSfti-hMeLeKdoXP7SVYqMUc,52
|
3
3
|
atlas_init/atlas_init.yaml,sha256=GMyJVhKKRc7WzEu7fafmWgeTsDaExTLv7QvXOmE_Brg,1907
|
4
4
|
atlas_init/cli.py,sha256=IiOEC_Jry6vrSDH3_OvsU50F-_3iVIS4tV6-R7659fY,9642
|
5
5
|
atlas_init/cli_args.py,sha256=tiwUYAE0JBSl9lHV6VJ41vFCU90ChBZ4mKvi-YoF_HY,541
|
6
6
|
atlas_init/humps.py,sha256=l0ZXXuI34wwd9TskXhCjULfGbUyK-qNmiyC6_2ow6kU,7339
|
7
7
|
atlas_init/terraform.yaml,sha256=qPrnbzBEP-JAQVkYadHsggRnDmshrOJyiv0ckyZCxwY,2734
|
8
|
-
atlas_init/typer_app.py,sha256=
|
8
|
+
atlas_init/typer_app.py,sha256=zbvYUlZrF4TZEPEwpa33fVSLVKcxRamuXCgF1FCUhCU,2068
|
9
9
|
atlas_init/cli_cfn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
atlas_init/cli_cfn/app.py,sha256=iMukpUDgsAgZh_U_APGZB3gmewOo-3vtFK0byJuDz9w,6649
|
11
11
|
atlas_init/cli_cfn/aws.py,sha256=GbohR7uczSGwQjLEYozCmlxbeIHo1uwQIJMwsh7kF7M,17894
|
@@ -21,16 +21,16 @@ atlas_init/cli_helper/tf_runner.py,sha256=OYdC-Y6i-xRh8_LCudKdtP7CEYEO9e67nVhhol
|
|
21
21
|
atlas_init/cli_root/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
atlas_init/cli_root/trigger.py,sha256=oEgqb_l25tyYgUaFHEuChcOCJA7k3mnRa4D-Myz-Igs,5789
|
23
23
|
atlas_init/cli_tf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
-
atlas_init/cli_tf/app.py,sha256
|
24
|
+
atlas_init/cli_tf/app.py,sha256=0Y5c-Pc9ibOz6kXvFlL-yhH_fx1nHLgBgK9OAVqjX9s,11390
|
25
25
|
atlas_init/cli_tf/changelog.py,sha256=biWYKf1pZvXZ-jEgcZ5q9sY7nTGrL2PuI0h9mCILf_g,3181
|
26
|
-
atlas_init/cli_tf/debug_logs.py,sha256=
|
27
|
-
atlas_init/cli_tf/debug_logs_test_data.py,sha256=
|
26
|
+
atlas_init/cli_tf/debug_logs.py,sha256=r9kRMzgZGP7SB0eK7hhbxETfI28yyHFtSpvSFe9NGC4,8839
|
27
|
+
atlas_init/cli_tf/debug_logs_test_data.py,sha256=G4pnuWJ7PAQd3NXRKAtwAPC6Ne-PgpzaTZHQ9waqxZI,9565
|
28
28
|
atlas_init/cli_tf/github_logs.py,sha256=VD7qhlXNuG21eTuJ5VI7rsflp5WHSodfngkRVgQlumw,8114
|
29
29
|
atlas_init/cli_tf/go_test_run.py,sha256=ZoQSvIasmWauFxZJrWL0ObFX-P0k-D3c_ep3OnPY4zs,5842
|
30
30
|
atlas_init/cli_tf/go_test_run_format.py,sha256=OUd6QPHDeTzbwVuh6MhP-xXgjOOGP9W_sCLJ8KylBTs,1201
|
31
31
|
atlas_init/cli_tf/go_test_summary.py,sha256=agr4SITgxchjgOzRpScoTUk-iG38QDLkpnsMtTW9GTY,5382
|
32
|
-
atlas_init/cli_tf/mock_tf_log.py,sha256=
|
33
|
-
atlas_init/cli_tf/schema.py,sha256=
|
32
|
+
atlas_init/cli_tf/mock_tf_log.py,sha256=u_d6c-lVo3eDddpW3V5r3FJfvQvoyrC8NnOGS1bYpR8,6572
|
33
|
+
atlas_init/cli_tf/schema.py,sha256=iwvb4wD2Wba0MMu7ooTNAIi1jHbpLiXGPOT51_o_YW8,12431
|
34
34
|
atlas_init/cli_tf/schema_go_parser.py,sha256=PiRfFFVnkhltxcGFfOCgH53wwzIEynw2BXmSfaINLL8,8294
|
35
35
|
atlas_init/cli_tf/schema_inspection.py,sha256=ujLvGfg3baByND4nRD0drZoI45STxo3VfYvim-PfVOc,1764
|
36
36
|
atlas_init/cli_tf/schema_table.py,sha256=1i6urBFNVpyopmLbDkYhL3pceKc9NJBCphfVIbm-K6Y,5229
|
@@ -50,14 +50,14 @@ atlas_init/cloud/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
50
50
|
atlas_init/cloud/aws.py,sha256=97kkURWHFAKDIw4704aFmyoeAfQKL11IXMyaQbZUt80,2473
|
51
51
|
atlas_init/repos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
52
52
|
atlas_init/repos/cfn.py,sha256=rjyVVxRhWL65tdAbEHT72UReK2h99Bj6RA4O2pBO-bc,2466
|
53
|
-
atlas_init/repos/go_sdk.py,sha256=
|
53
|
+
atlas_init/repos/go_sdk.py,sha256=1OzM9DjHEAzAAuI9ygoRRuhUK2gqpOhXExXRqhqa0tg,1793
|
54
54
|
atlas_init/repos/path.py,sha256=wrT8e01OBoAHj8iMrxqutgqWu-BHPe9-bEWtcZRu238,4187
|
55
55
|
atlas_init/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
56
|
atlas_init/settings/config.py,sha256=HIytZom8RRvpLGy6u8CpZ83tmFXI6v1tO3iSiuo08kc,6259
|
57
57
|
atlas_init/settings/env_vars.py,sha256=q8Hj2LPJIg-PK0fCjrEigoPwTGIEbqjLEZckwgnkG8s,9688
|
58
58
|
atlas_init/settings/interactive.py,sha256=Xy1Z5WMAOSaJ-vQI_4xjAbSR92rWQgnffwVoDT27L68,340
|
59
|
-
atlas_init/settings/path.py,sha256=
|
60
|
-
atlas_init/settings/rich_utils.py,sha256=
|
59
|
+
atlas_init/settings/path.py,sha256=KkXysu6-0AuSjsvYGknYGJX1hL2j1RD-Fpf8KsVYpkE,2618
|
60
|
+
atlas_init/settings/rich_utils.py,sha256=5LgJUmc9wyJTsoS6xWKadrT0MoQREDaKvEOCuBLDXRg,1704
|
61
61
|
atlas_init/tf/.terraform.lock.hcl,sha256=DIojR50rr4fyLShYiQ-UpRV8z6vuBjwGWdK60FODoyM,6876
|
62
62
|
atlas_init/tf/always.tf,sha256=ij6QKI8Lg0140bFZwOyiYK5c-2p5e7AGZ1qKbYyv6Os,1359
|
63
63
|
atlas_init/tf/main.tf,sha256=DH0C8y9RDEHnSAZvL-TjE5MQjxj5ALfgk5zVO88cpZw,3960
|
@@ -86,7 +86,7 @@ atlas_init/tf/modules/vpc_peering/vpc_peering.tf,sha256=hJ3KJdGbLpOQednUpVuiJ0Cq
|
|
86
86
|
atlas_init/tf/modules/vpc_privatelink/atlas-privatelink.tf,sha256=FloaaX1MNDvoMZxBnEopeLKyfIlq6kaX2dmx8WWlXNU,1298
|
87
87
|
atlas_init/tf/modules/vpc_privatelink/variables.tf,sha256=gktHCDYD4rz6CEpLg5aiXcFbugw4L5S2Fqc52QYdJyc,255
|
88
88
|
atlas_init/tf/modules/vpc_privatelink/versions.tf,sha256=G0u5V_Hvvrkux_tqfOY05pA-GzSp_qILpfx1dZaTGDc,237
|
89
|
-
atlas_init-0.3.
|
90
|
-
atlas_init-0.3.
|
91
|
-
atlas_init-0.3.
|
92
|
-
atlas_init-0.3.
|
89
|
+
atlas_init-0.3.2.dist-info/METADATA,sha256=foETVpu_FKi1rkGOJB158pmAftoNYFiG9VNr1kPBPvA,5650
|
90
|
+
atlas_init-0.3.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
91
|
+
atlas_init-0.3.2.dist-info/entry_points.txt,sha256=oSNFIEAS9nUZyyZ8Fc-0F0U5j-NErygy01LpJVSHapQ,57
|
92
|
+
atlas_init-0.3.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|