atlas-init 0.3.2__py3-none-any.whl → 0.3.4__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 +7 -0
- atlas_init/cli.py +0 -18
- atlas_init/cli_helper/go.py +196 -13
- atlas_init/cli_helper/run.py +10 -0
- atlas_init/cli_root/go_test.py +95 -0
- atlas_init/cli_tf/debug_logs.py +7 -5
- atlas_init/cli_tf/debug_logs_test_data_package_config.py +48 -0
- atlas_init/cli_tf/go_test_run.py +39 -9
- atlas_init/cli_tf/mock_tf_log.py +6 -0
- atlas_init/settings/env_vars.py +4 -1
- atlas_init/typer_app.py +2 -1
- {atlas_init-0.3.2.dist-info → atlas_init-0.3.4.dist-info}/METADATA +1 -1
- {atlas_init-0.3.2.dist-info → atlas_init-0.3.4.dist-info}/RECORD +16 -14
- {atlas_init-0.3.2.dist-info → atlas_init-0.3.4.dist-info}/WHEEL +0 -0
- {atlas_init-0.3.2.dist-info → atlas_init-0.3.4.dist-info}/entry_points.txt +0 -0
atlas_init/__init__.py
CHANGED
atlas_init/atlas_init.yaml
CHANGED
@@ -6,6 +6,10 @@ test_suites:
|
|
6
6
|
repo_go_packages:
|
7
7
|
cfn:
|
8
8
|
- cfn-resources/cluster
|
9
|
+
- name: cluster_sdk
|
10
|
+
repo_go_packages:
|
11
|
+
tf:
|
12
|
+
- internal/service/advancedcluster
|
9
13
|
- name: clusterm10
|
10
14
|
vars:
|
11
15
|
cluster_info_m10: true
|
@@ -53,9 +57,12 @@ test_suites:
|
|
53
57
|
vars:
|
54
58
|
use_project_extra: true
|
55
59
|
- name: resource_policy
|
60
|
+
sequential_tests: true
|
56
61
|
repo_go_packages:
|
57
62
|
cfn:
|
58
63
|
- cfn-resources/resource-policy
|
64
|
+
tf:
|
65
|
+
- internal/service/resourcepolicy
|
59
66
|
- name: s3
|
60
67
|
repo_go_packages:
|
61
68
|
tf:
|
atlas_init/cli.py
CHANGED
@@ -9,7 +9,6 @@ from model_lib import dump, parse_payload
|
|
9
9
|
from zero_3rdparty.file_utils import iter_paths
|
10
10
|
|
11
11
|
from atlas_init.cli_helper import sdk_auto_changes
|
12
|
-
from atlas_init.cli_helper.go import run_go_tests
|
13
12
|
from atlas_init.cli_helper.run import (
|
14
13
|
run_binary_command_is_ok,
|
15
14
|
run_command_exit_on_failure,
|
@@ -116,23 +115,6 @@ def destroy(context: typer.Context):
|
|
116
115
|
return
|
117
116
|
|
118
117
|
|
119
|
-
@app_command()
|
120
|
-
def test_go():
|
121
|
-
settings = init_settings()
|
122
|
-
suites = active_suites(settings)
|
123
|
-
sorted_suites = sorted(suite.name for suite in suites)
|
124
|
-
logger.info(f"running go tests for {len(suites)} test-suites: {sorted_suites}")
|
125
|
-
match repo_alias := current_repo():
|
126
|
-
case Repo.CFN:
|
127
|
-
raise NotImplementedError
|
128
|
-
case Repo.TF:
|
129
|
-
repo_path = current_repo_path()
|
130
|
-
package_prefix = settings.config.go_package_prefix(repo_alias)
|
131
|
-
run_go_tests(repo_path, repo_alias, package_prefix, settings, suites)
|
132
|
-
case _:
|
133
|
-
raise NotImplementedError
|
134
|
-
|
135
|
-
|
136
118
|
@app_command()
|
137
119
|
def sdk_upgrade(
|
138
120
|
old: SdkVersion = typer.Argument(help=SDK_VERSION_HELP),
|
atlas_init/cli_helper/go.py
CHANGED
@@ -1,34 +1,217 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
|
+
from concurrent.futures import ThreadPoolExecutor, wait
|
4
|
+
from enum import StrEnum
|
3
5
|
from pathlib import Path
|
4
6
|
|
5
|
-
from
|
7
|
+
from model_lib import Entity
|
8
|
+
from pydantic import Field
|
9
|
+
|
10
|
+
from atlas_init.cli_helper.run import run_command_is_ok_output
|
11
|
+
from atlas_init.cli_tf.go_test_run import (
|
12
|
+
GoTestContext,
|
13
|
+
GoTestContextStep,
|
14
|
+
GoTestRun,
|
15
|
+
parse,
|
16
|
+
)
|
6
17
|
from atlas_init.settings.config import TestSuite
|
7
18
|
from atlas_init.settings.env_vars import AtlasInitSettings
|
19
|
+
from atlas_init.settings.path import DEFAULT_DOWNLOADS_DIR
|
8
20
|
|
9
21
|
logger = logging.getLogger(__name__)
|
10
22
|
|
11
23
|
|
24
|
+
class GoTestMode(StrEnum):
|
25
|
+
package = "package"
|
26
|
+
individual = "individual"
|
27
|
+
|
28
|
+
|
29
|
+
class GoEnvVars(StrEnum):
|
30
|
+
manual = "manual"
|
31
|
+
vscode = "vscode"
|
32
|
+
|
33
|
+
|
34
|
+
class GoTestResult(Entity):
|
35
|
+
runs: dict[str, list[GoTestRun]] = Field(default_factory=dict)
|
36
|
+
failure_names: set[str] = Field(default_factory=set)
|
37
|
+
|
38
|
+
test_name_package_path: dict[str, Path] = Field(default_factory=dict)
|
39
|
+
|
40
|
+
def add_test_package_path(self, test_name: str, package_path: Path):
|
41
|
+
if old_path := self.test_name_package_path.get(test_name):
|
42
|
+
logger.warning(f"overwriting test_name={test_name} with package_path={old_path} --> {package_path}")
|
43
|
+
self.test_name_package_path[test_name] = package_path
|
44
|
+
|
45
|
+
def add_test_results_all_pass(self, test_name: str, test_results: list[GoTestRun]) -> bool:
|
46
|
+
prev_test_results = self.runs.setdefault(test_name, [])
|
47
|
+
if prev_test_results:
|
48
|
+
logger.warning(f"2nd time test results for {test_name}")
|
49
|
+
for result in test_results:
|
50
|
+
log_path = _log_path(test_name)
|
51
|
+
result.log_path = log_path
|
52
|
+
prev_test_results.extend(test_results)
|
53
|
+
return all(run.is_pass for run in test_results)
|
54
|
+
|
55
|
+
|
12
56
|
def run_go_tests(
|
13
57
|
repo_path: Path,
|
14
58
|
repo_alias: str,
|
15
59
|
package_prefix: str,
|
16
60
|
settings: AtlasInitSettings,
|
17
61
|
groups: list[TestSuite],
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
62
|
+
mode: GoTestMode = GoTestMode.package,
|
63
|
+
*,
|
64
|
+
dry_run: bool = False,
|
65
|
+
timeout_minutes: int = 300,
|
66
|
+
concurrent_runs: int = 20,
|
67
|
+
re_run: bool = False,
|
68
|
+
env_vars: GoEnvVars = GoEnvVars.vscode,
|
69
|
+
) -> GoTestResult:
|
70
|
+
test_env = _resolve_env_vars(settings, env_vars)
|
71
|
+
if ci_value := test_env.pop("CI", None):
|
24
72
|
logger.warning(f"pooped CI={ci_value}")
|
73
|
+
results = GoTestResult()
|
74
|
+
commands_to_run: dict[str, str] = {}
|
25
75
|
for group in groups:
|
26
|
-
|
76
|
+
package_paths = group.repo_go_packages.get(repo_alias, [])
|
77
|
+
packages = ",".join(f"{package_prefix}/{pkg}" for pkg in package_paths)
|
27
78
|
if not packages:
|
28
79
|
logger.warning(f"no go packages for suite: {group}")
|
29
80
|
continue
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
81
|
+
if mode == GoTestMode.individual:
|
82
|
+
if group.sequential_tests:
|
83
|
+
logger.info(f"running individual tests sequentially as {group.name} is set to sequential_tests")
|
84
|
+
concurrent_runs = 1
|
85
|
+
test_names = find_individual_tests(repo_path, package_paths)
|
86
|
+
for name, pkg_path in test_names.items():
|
87
|
+
results.add_test_package_path(name, pkg_path)
|
88
|
+
commands_to_run[name] = f"go test {packages} -v -run ^{name}$ -timeout {timeout_minutes}m"
|
89
|
+
elif mode == GoTestMode.package:
|
90
|
+
command = f"go test {packages} -v -run ^TestAcc* -timeout {timeout_minutes}m"
|
91
|
+
if not group.sequential_tests:
|
92
|
+
command = f"{command} -parallel {concurrent_runs}"
|
93
|
+
commands_to_run[group.name] = command
|
94
|
+
else:
|
95
|
+
raise NotImplementedError(f"mode={mode}")
|
96
|
+
commands_str = "\n".join(f"'{name}': '{command}'" for name, command in sorted(commands_to_run.items()))
|
97
|
+
logger.info(f"will run the following commands:\n{commands_str}")
|
98
|
+
if dry_run:
|
99
|
+
return results
|
100
|
+
if not commands_to_run:
|
101
|
+
logger.warning("no tests to run!")
|
102
|
+
return results
|
103
|
+
return _run_tests(
|
104
|
+
results,
|
105
|
+
repo_path,
|
106
|
+
commands_to_run,
|
107
|
+
test_env,
|
108
|
+
test_timeout_s=timeout_minutes * 60,
|
109
|
+
max_workers=concurrent_runs,
|
110
|
+
re_run=re_run,
|
111
|
+
)
|
112
|
+
|
113
|
+
|
114
|
+
def _resolve_env_vars(settings: AtlasInitSettings, env_vars: GoEnvVars) -> dict[str, str]:
|
115
|
+
if env_vars == GoEnvVars.manual:
|
116
|
+
extra_vars = settings.load_profile_manual_env_vars(skip_os_update=True)
|
117
|
+
elif env_vars == GoEnvVars.vscode:
|
118
|
+
extra_vars = settings.load_env_vars(settings.env_vars_vs_code)
|
119
|
+
else:
|
120
|
+
raise NotImplementedError(f"don't know how to load env_vars={env_vars}")
|
121
|
+
test_env = os.environ | extra_vars | {"TF_ACC": "1", "TF_LOG": "DEBUG"}
|
122
|
+
logger.info(f"go test env-vars-extra: {sorted(extra_vars)}")
|
123
|
+
return test_env
|
124
|
+
|
125
|
+
|
126
|
+
def find_individual_tests(repo_path: Path, package_paths: list[str]) -> dict[str, Path]:
|
127
|
+
tests = {}
|
128
|
+
for package_path in package_paths:
|
129
|
+
package_abs_path = repo_path / package_path.lstrip(".").lstrip("/")
|
130
|
+
for go_file in package_abs_path.glob("*.go"):
|
131
|
+
with go_file.open() as f:
|
132
|
+
for line in f:
|
133
|
+
if line.startswith("func TestAcc"):
|
134
|
+
test_name = line.split("(")[0].strip().removeprefix("func ")
|
135
|
+
tests[test_name] = package_abs_path
|
136
|
+
return tests
|
137
|
+
|
138
|
+
|
139
|
+
def _run_tests(
|
140
|
+
results: GoTestResult,
|
141
|
+
repo_path: Path,
|
142
|
+
commands_to_run: dict[str, str],
|
143
|
+
test_env: dict[str, str],
|
144
|
+
test_timeout_s: int = 301 * 60,
|
145
|
+
max_workers: int = 2,
|
146
|
+
*,
|
147
|
+
re_run: bool = False,
|
148
|
+
) -> GoTestResult:
|
149
|
+
futures = {}
|
150
|
+
actual_workers = min(max_workers, len(commands_to_run)) or 1
|
151
|
+
with ThreadPoolExecutor(max_workers=actual_workers) as pool:
|
152
|
+
for name, command in sorted(commands_to_run.items()):
|
153
|
+
log_path = _log_path(name)
|
154
|
+
if log_path.exists() and log_path.read_text() and not re_run:
|
155
|
+
logger.info(f"skipping {name} because log exists")
|
156
|
+
continue
|
157
|
+
command_env = {**test_env, "TF_LOG_PATH": str(log_path)}
|
158
|
+
future = pool.submit(
|
159
|
+
run_command_is_ok_output,
|
160
|
+
command=command,
|
161
|
+
env=command_env,
|
162
|
+
cwd=repo_path,
|
163
|
+
logger=logger,
|
164
|
+
)
|
165
|
+
futures[future] = name
|
166
|
+
done, not_done = wait(futures.keys(), timeout=test_timeout_s)
|
167
|
+
for f in not_done:
|
168
|
+
logger.warning(f"timeout to run command name = {futures[f]}")
|
169
|
+
for f in done:
|
170
|
+
name: str = futures[f]
|
171
|
+
try:
|
172
|
+
ok, command_out = f.result()
|
173
|
+
except Exception:
|
174
|
+
logger.exception(f"failed to run command for {name}")
|
175
|
+
results.failure_names.add(name)
|
176
|
+
continue
|
177
|
+
context = GoTestContext(
|
178
|
+
name=name,
|
179
|
+
html_url=f"file://{_log_path(name)}",
|
180
|
+
steps=[GoTestContextStep(name="local-run")],
|
181
|
+
)
|
182
|
+
try:
|
183
|
+
parsed_tests = list(parse(command_out.splitlines(), context, test_step_nr=0))
|
184
|
+
except Exception:
|
185
|
+
logger.exception(f"failed to parse tests for {name}")
|
186
|
+
results.failure_names.add(name)
|
187
|
+
continue
|
188
|
+
if not parsed_tests and not ok:
|
189
|
+
results.failure_names.add(name)
|
190
|
+
logger.error(f"failed to run tests for {name}: {command_out}")
|
191
|
+
continue
|
192
|
+
if not parsed_tests:
|
193
|
+
logger.warning(f"failed to parse tests for {name}: {command_out}")
|
194
|
+
continue
|
195
|
+
if not ok:
|
196
|
+
logger.warning(f"failing tests for {name}: {command_out}")
|
197
|
+
if not results.add_test_results_all_pass(name, parsed_tests):
|
198
|
+
results.failure_names.add(name)
|
199
|
+
if failure_names := results.failure_names:
|
200
|
+
move_failed_logs_to_error_dir(failure_names)
|
201
|
+
logger.error(f"failed to run tests: {sorted(failure_names)}")
|
202
|
+
return results
|
203
|
+
|
204
|
+
|
205
|
+
def move_failed_logs_to_error_dir(failures: set[str]):
|
206
|
+
error_dir = DEFAULT_DOWNLOADS_DIR / "failures"
|
207
|
+
for log in DEFAULT_DOWNLOADS_DIR.glob("*.log"):
|
208
|
+
if log.stem in failures:
|
209
|
+
text = log.read_text()
|
210
|
+
assert "\n" in text
|
211
|
+
first_line = text.split("\n", maxsplit=1)[0]
|
212
|
+
ts = first_line.split(" ")[0]
|
213
|
+
log.rename(error_dir / f"{ts}.{log.name}")
|
214
|
+
|
215
|
+
|
216
|
+
def _log_path(name: str) -> Path:
|
217
|
+
return DEFAULT_DOWNLOADS_DIR / f"{name}.log"
|
atlas_init/cli_helper/run.py
CHANGED
@@ -8,6 +8,7 @@ from tempfile import TemporaryDirectory
|
|
8
8
|
from typing import IO, TypeVar
|
9
9
|
|
10
10
|
import typer
|
11
|
+
from zero_3rdparty.id_creator import simple_id
|
11
12
|
|
12
13
|
StrT = TypeVar("StrT", bound=str)
|
13
14
|
|
@@ -94,6 +95,15 @@ def run_command_receive_result(
|
|
94
95
|
return output_text
|
95
96
|
|
96
97
|
|
98
|
+
def run_command_is_ok_output(command: str, cwd: Path, logger: Logger, env: dict | None = None) -> tuple[bool, str]:
|
99
|
+
with TemporaryDirectory() as temp_dir:
|
100
|
+
result_file = Path(temp_dir) / f"{simple_id()}.txt"
|
101
|
+
with open(result_file, "w") as file:
|
102
|
+
is_ok = run_command_is_ok(command.split(), env=env, cwd=cwd, logger=logger, output=file)
|
103
|
+
output_text = result_file.read_text().strip()
|
104
|
+
return is_ok, output_text
|
105
|
+
|
106
|
+
|
97
107
|
def add_to_clipboard(clipboard_content: str, logger: Logger):
|
98
108
|
if pb_binary := find_binary_on_path("pbcopy", logger, allow_missing=True):
|
99
109
|
subprocess.run(pb_binary, text=True, input=clipboard_content, check=True)
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
import typer
|
4
|
+
|
5
|
+
from atlas_init.cli_helper.go import GoEnvVars, GoTestMode, GoTestResult, run_go_tests
|
6
|
+
from atlas_init.cli_tf.mock_tf_log import MockTFLog, mock_tf_log, resolve_admin_api_path
|
7
|
+
from atlas_init.repos.path import Repo, current_repo, current_repo_path
|
8
|
+
from atlas_init.settings.env_vars import active_suites, init_settings
|
9
|
+
from atlas_init.typer_app import app_command
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
@app_command()
|
15
|
+
def go_test(
|
16
|
+
mode: GoTestMode = typer.Option("package", "-m", "--mode", help="package|individual"),
|
17
|
+
dry_run: bool = typer.Option(False, help="only log out the commands to be run"),
|
18
|
+
timeout_minutes: int = typer.Option(300, "-t", "--timeout", help="timeout in minutes"),
|
19
|
+
concurrent_runs: int = typer.Option(20, "-c", "--concurrent", help="number of concurrent runs"),
|
20
|
+
re_run: bool = typer.Option(False, "-r", "--re-run", help="re-run the tests if the log already exist"),
|
21
|
+
export_mock_tf_log: bool = typer.Option(False, "-e", "--export", help="export the mock-tf-log"),
|
22
|
+
export_mock_tf_log_verbose: bool = typer.Option(
|
23
|
+
False, "--export-verbose", help="log roundtrips when exporting the mock-tf-log"
|
24
|
+
),
|
25
|
+
env_method: GoEnvVars = typer.Option(GoEnvVars.manual, "--env", help="|".join(list(GoEnvVars))),
|
26
|
+
):
|
27
|
+
if export_mock_tf_log and mode != GoTestMode.individual:
|
28
|
+
err_msg = "exporting mock-tf-log is only supported for individual tests"
|
29
|
+
raise ValueError(err_msg)
|
30
|
+
settings = init_settings()
|
31
|
+
suites = active_suites(settings)
|
32
|
+
sorted_suites = sorted(suite.name for suite in suites)
|
33
|
+
logger.info(f"running go tests for {len(suites)} test-suites: {sorted_suites}")
|
34
|
+
results: GoTestResult | None = None
|
35
|
+
match repo_alias := current_repo():
|
36
|
+
case Repo.CFN:
|
37
|
+
raise NotImplementedError
|
38
|
+
case Repo.TF:
|
39
|
+
repo_path = current_repo_path()
|
40
|
+
package_prefix = settings.config.go_package_prefix(repo_alias)
|
41
|
+
results = run_go_tests(
|
42
|
+
repo_path,
|
43
|
+
repo_alias,
|
44
|
+
package_prefix,
|
45
|
+
settings,
|
46
|
+
suites,
|
47
|
+
mode,
|
48
|
+
dry_run=dry_run,
|
49
|
+
timeout_minutes=timeout_minutes,
|
50
|
+
concurrent_runs=concurrent_runs,
|
51
|
+
re_run=re_run,
|
52
|
+
env_vars=env_method,
|
53
|
+
)
|
54
|
+
case _:
|
55
|
+
raise NotImplementedError
|
56
|
+
if results is None:
|
57
|
+
error_msg = "no results found"
|
58
|
+
raise ValueError(error_msg)
|
59
|
+
if export_mock_tf_log:
|
60
|
+
_export_mock_tf_logs(results, export_mock_tf_log_verbose)
|
61
|
+
# use the test_results: dict[str, list[GoTestRun]]
|
62
|
+
# TODO: create_detailed_summary()
|
63
|
+
|
64
|
+
|
65
|
+
def _export_mock_tf_logs(results: GoTestResult, verbose: bool):
|
66
|
+
package_paths = results.test_name_package_path
|
67
|
+
admin_api_path = resolve_admin_api_path("", sdk_branch="main", admin_api_path="")
|
68
|
+
for test_name, runs in results.runs.items():
|
69
|
+
package_path = package_paths.get(test_name)
|
70
|
+
if package_path is None:
|
71
|
+
logger.warning(f"no package path found for test_name={test_name}")
|
72
|
+
continue
|
73
|
+
assert len(runs) == 1, f"expected only 1 run for test_name={test_name}, got {len(runs)}"
|
74
|
+
run = runs[0]
|
75
|
+
tpf_package_path = package_path.with_name(f"{package_path.name}tpf") / "testdata"
|
76
|
+
default_package_path = package_path / "testdata"
|
77
|
+
if not tpf_package_path.exists():
|
78
|
+
logger.warning(
|
79
|
+
f"tpf_package_path={tpf_package_path} doesn't exist, adding mocked data to {default_package_path}"
|
80
|
+
)
|
81
|
+
tpf_package_path = default_package_path
|
82
|
+
tf_log_path = run.log_path
|
83
|
+
assert tf_log_path, f"test didn't set tf_log_path: {test_name}"
|
84
|
+
if test_name in results.failure_names:
|
85
|
+
logger.warning(f"test_name={test_name} failed, not exporting mock-tf-log")
|
86
|
+
continue
|
87
|
+
req = MockTFLog(
|
88
|
+
log_path=tf_log_path,
|
89
|
+
output_dir=tpf_package_path,
|
90
|
+
admin_api_path=admin_api_path,
|
91
|
+
package_name=package_path.name,
|
92
|
+
log_diff_roundtrips=verbose,
|
93
|
+
)
|
94
|
+
mocked_yaml = mock_tf_log(req)
|
95
|
+
logger.info(f"mocked TestConfig saved to {mocked_yaml}")
|
atlas_init/cli_tf/debug_logs.py
CHANGED
@@ -11,13 +11,15 @@ from pydantic import ValidationError, model_validator
|
|
11
11
|
logger = logging.getLogger(__name__)
|
12
12
|
|
13
13
|
|
14
|
-
def parsed(payload: str) -> tuple[dict[str, Any], list]:
|
14
|
+
def parsed(payload: str) -> tuple[dict[str, Any] | None, list | None, bool | None]:
|
15
15
|
with suppress(ValueError):
|
16
16
|
resp = json.loads(payload)
|
17
17
|
if isinstance(resp, dict):
|
18
|
-
return resp,
|
18
|
+
return resp, None, None
|
19
19
|
if isinstance(resp, list):
|
20
|
-
return
|
20
|
+
return None, resp, None
|
21
|
+
if payload.strip() in {"true", "false"}:
|
22
|
+
return None, None, payload.strip() == "true"
|
21
23
|
raise ValueError(f"Could not parse payload: {payload}")
|
22
24
|
|
23
25
|
|
@@ -109,8 +111,8 @@ class SDKRoundtrip(Entity):
|
|
109
111
|
def ensure_match(self) -> Self:
|
110
112
|
req = self.request
|
111
113
|
resp = self.response
|
112
|
-
_, resp_payload_list = parsed(resp.text)
|
113
|
-
if req.expect_list_response and
|
114
|
+
_, resp_payload_list, __ = parsed(resp.text)
|
115
|
+
if req.expect_list_response and resp_payload_list is None:
|
114
116
|
raise ValueError(f"Expected list response but got dict: {resp.text}")
|
115
117
|
return self
|
116
118
|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import json
|
2
|
+
import logging
|
3
|
+
|
4
|
+
from atlas_init.cli_tf.debug_logs import SDKRoundtrip, parsed
|
5
|
+
from atlas_init.cli_tf.debug_logs_test_data import RTModifier
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
|
10
|
+
def add_label_tags(rt: SDKRoundtrip):
|
11
|
+
logger.info(f"Adding labels and tags to {rt.id}")
|
12
|
+
request = rt.request
|
13
|
+
req_dict, req_list, req_bool = parsed(request.text)
|
14
|
+
response = rt.response
|
15
|
+
resp_dict, resp_list, req_bool = parsed(response.text)
|
16
|
+
if resp_list or req_list or req_bool is not None:
|
17
|
+
return
|
18
|
+
resp_dict = resp_dict or {}
|
19
|
+
req_dict = req_dict or {}
|
20
|
+
for extra_field in ["labels", "tags"]:
|
21
|
+
if extra_field not in resp_dict:
|
22
|
+
resp_dict[extra_field] = []
|
23
|
+
if extra_field not in req_dict:
|
24
|
+
req_dict[extra_field] = []
|
25
|
+
request.text = json.dumps(req_dict, indent=1, sort_keys=True)
|
26
|
+
response.text = json.dumps(resp_dict, indent=1, sort_keys=True)
|
27
|
+
|
28
|
+
|
29
|
+
cluster_modifier = RTModifier(
|
30
|
+
version="2024-08-05",
|
31
|
+
method="POST",
|
32
|
+
path="/api/atlas/v2/groups/{groupId}/clusters",
|
33
|
+
modification=add_label_tags,
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
def package_modifiers(pkg_name: str) -> list[RTModifier]:
|
38
|
+
# sourcery skip: assign-if-exp, reintroduce-else
|
39
|
+
if pkg_name == "advancedcluster":
|
40
|
+
return [cluster_modifier]
|
41
|
+
return []
|
42
|
+
|
43
|
+
|
44
|
+
def package_skip_suffixes(pkg_name: str) -> list[str]:
|
45
|
+
# sourcery skip: assign-if-exp, reintroduce-else
|
46
|
+
if pkg_name == "resourcepolicy":
|
47
|
+
return [":validate"]
|
48
|
+
return []
|
atlas_init/cli_tf/go_test_run.py
CHANGED
@@ -5,11 +5,13 @@ import re
|
|
5
5
|
from collections.abc import Iterable
|
6
6
|
from enum import StrEnum
|
7
7
|
from functools import total_ordering
|
8
|
+
from pathlib import Path
|
8
9
|
|
9
10
|
import humanize
|
10
11
|
from github.WorkflowJob import WorkflowJob
|
11
12
|
from model_lib import Entity, Event, utc_datetime
|
12
|
-
from pydantic import Field
|
13
|
+
from pydantic import Field, field_validator
|
14
|
+
from zero_3rdparty.datetime_utils import utc_now
|
13
15
|
|
14
16
|
logger = logging.getLogger(__name__)
|
15
17
|
|
@@ -35,6 +37,24 @@ class LineInfo(Event):
|
|
35
37
|
text: str
|
36
38
|
|
37
39
|
|
40
|
+
class GoTestContextStep(Entity):
|
41
|
+
name: str
|
42
|
+
|
43
|
+
|
44
|
+
class GoTestContext(Entity):
|
45
|
+
"""Abstraction on WorkflowJob to also support local runs"""
|
46
|
+
|
47
|
+
name: str
|
48
|
+
created_at: utc_datetime = Field(default_factory=utc_now)
|
49
|
+
steps: list[GoTestContextStep] = Field(default_factory=list)
|
50
|
+
html_url: str = "http://localhost"
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def from_local_run(cls, name: str, steps: list[GoTestContextStep]) -> GoTestContext:
|
54
|
+
raise NotImplementedError
|
55
|
+
# return cls(name=name, steps=steps)
|
56
|
+
|
57
|
+
|
38
58
|
@total_ordering
|
39
59
|
class GoTestRun(Entity):
|
40
60
|
name: str
|
@@ -42,8 +62,9 @@ class GoTestRun(Entity):
|
|
42
62
|
start_line: LineInfo
|
43
63
|
ts: utc_datetime
|
44
64
|
finish_ts: utc_datetime | None = None
|
45
|
-
job: WorkflowJob
|
65
|
+
job: GoTestContext | WorkflowJob
|
46
66
|
test_step: int
|
67
|
+
log_path: Path | None = None
|
47
68
|
|
48
69
|
finish_line: LineInfo | None = None
|
49
70
|
context_lines: list[str] = Field(default_factory=list)
|
@@ -87,6 +108,10 @@ class GoTestRun(Entity):
|
|
87
108
|
def is_failure(self) -> bool:
|
88
109
|
return self.status == GoTestStatus.FAIL
|
89
110
|
|
111
|
+
@property
|
112
|
+
def is_pass(self) -> bool:
|
113
|
+
return self.status == GoTestStatus.PASS
|
114
|
+
|
90
115
|
def add_line_match(self, match: LineMatch, line: str, line_number: int) -> None:
|
91
116
|
self.run_seconds = match.run_seconds or self.run_seconds
|
92
117
|
self.finish_line = LineInfo(number=line_number, text=line)
|
@@ -99,7 +124,7 @@ class GoTestRun(Entity):
|
|
99
124
|
match: LineMatch,
|
100
125
|
line: str,
|
101
126
|
line_number: int,
|
102
|
-
job: WorkflowJob,
|
127
|
+
job: WorkflowJob | GoTestContext,
|
103
128
|
test_step_nr: int,
|
104
129
|
) -> GoTestRun:
|
105
130
|
start_line = LineInfo(number=line_number, text=line)
|
@@ -115,15 +140,20 @@ class GoTestRun(Entity):
|
|
115
140
|
|
116
141
|
|
117
142
|
class LineMatch(Event):
|
118
|
-
ts: utc_datetime
|
143
|
+
ts: utc_datetime = Field(default_factory=utc_now)
|
119
144
|
status: GoTestStatus
|
120
145
|
name: str
|
121
146
|
run_seconds: float | None = None
|
122
147
|
|
148
|
+
@field_validator("ts", mode="before")
|
149
|
+
@classmethod
|
150
|
+
def remove_none(cls, v):
|
151
|
+
return v or utc_now()
|
152
|
+
|
123
153
|
|
124
154
|
_status_options = "|".join(list(GoTestStatus))
|
125
155
|
line_result = re.compile(
|
126
|
-
r"(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z?)
|
156
|
+
r"(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z?)?\s?[-=]+\s"
|
127
157
|
+ r"(?P<status>%s):?\s+" % _status_options # noqa: UP031
|
128
158
|
+ r"(?P<name>[\w_]+)"
|
129
159
|
+ r"\s*\(?(?P<run_seconds>[\d\.]+)?s?\)?"
|
@@ -140,13 +170,13 @@ def match_line(line: str) -> LineMatch | None:
|
|
140
170
|
2024-06-26T04:41:47.7228652Z --- PASS: TestAccNetworkRSPrivateLinkEndpointGCP_basic (424.50s)
|
141
171
|
"""
|
142
172
|
if match := line_result.match(line):
|
143
|
-
line_match = LineMatch(**match.groupdict())
|
173
|
+
line_match = LineMatch(**match.groupdict()) # type: ignore
|
144
174
|
return None if _test_name_is_nested(line_match.name, line) else line_match
|
145
175
|
return None
|
146
176
|
|
147
177
|
|
148
178
|
context_start_pattern = re.compile(
|
149
|
-
r"(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z?)
|
179
|
+
r"(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z?)?\s?[-=]+\s" r"NAME\s+" r"(?P<name>[\w_]+)"
|
150
180
|
)
|
151
181
|
|
152
182
|
|
@@ -157,7 +187,7 @@ def context_start_match(line: str) -> str:
|
|
157
187
|
|
158
188
|
|
159
189
|
context_line_pattern = re.compile(
|
160
|
-
r"(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z?)" r"\s{5}" r"(?P<indent>\s*)" r"(?P<relevant_line>.*)"
|
190
|
+
r"(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z?)?" r"\s{5}" r"(?P<indent>\s*)" r"(?P<relevant_line>.*)"
|
161
191
|
)
|
162
192
|
|
163
193
|
|
@@ -168,7 +198,7 @@ def extract_context(line: str) -> str:
|
|
168
198
|
return ""
|
169
199
|
|
170
200
|
|
171
|
-
def parse(test_lines: list[str], job: WorkflowJob, test_step_nr: int) -> Iterable[GoTestRun]:
|
201
|
+
def parse(test_lines: list[str], job: WorkflowJob | GoTestContext, test_step_nr: int) -> Iterable[GoTestRun]:
|
172
202
|
tests: dict[str, GoTestRun] = {}
|
173
203
|
context_lines: list[str] = []
|
174
204
|
current_context_test = ""
|
atlas_init/cli_tf/mock_tf_log.py
CHANGED
@@ -23,6 +23,7 @@ from atlas_init.cli_tf.debug_logs_test_data import (
|
|
23
23
|
create_mock_data,
|
24
24
|
default_is_diff,
|
25
25
|
)
|
26
|
+
from atlas_init.cli_tf.debug_logs_test_data_package_config import package_modifiers, package_skip_suffixes
|
26
27
|
from atlas_init.repos.go_sdk import (
|
27
28
|
api_spec_path_transformed,
|
28
29
|
download_admin_api,
|
@@ -40,7 +41,9 @@ class MockTFLog(Entity):
|
|
40
41
|
diff_skip_suffixes: list[str] = Field(default_factory=list)
|
41
42
|
keep_duplicates: bool = False
|
42
43
|
modifiers: list[RTModifier] = Field(default_factory=list)
|
44
|
+
package_name: str = ""
|
43
45
|
log_diff_roundtrips: bool = False
|
46
|
+
skip_default_package_config: bool = False
|
44
47
|
|
45
48
|
@model_validator(mode="after")
|
46
49
|
def ensure_paths_exist(self) -> Self:
|
@@ -51,6 +54,9 @@ class MockTFLog(Entity):
|
|
51
54
|
if not self.output_dir.exists():
|
52
55
|
raise ValueError(f"output_dir: '{self.output_dir}' doesn't exist")
|
53
56
|
assert self.output_dir.name == "testdata", "output_path should be a directory named testdata"
|
57
|
+
if (package_name := self.package_name) and not self.skip_default_package_config:
|
58
|
+
self.modifiers.extend(package_modifiers(package_name))
|
59
|
+
self.diff_skip_suffixes.extend(package_skip_suffixes(package_name))
|
54
60
|
return self
|
55
61
|
|
56
62
|
def differ(self, rt: SDKRoundtrip) -> bool:
|
atlas_init/settings/env_vars.py
CHANGED
@@ -170,9 +170,12 @@ class AtlasInitPaths(BaseSettings):
|
|
170
170
|
assert env_path.exists(), f"no env-vars exist {env_path} have you forgotten apply?"
|
171
171
|
return load_dotenv(env_path)
|
172
172
|
|
173
|
-
def load_profile_manual_env_vars(self) -> dict[str, str]:
|
173
|
+
def load_profile_manual_env_vars(self, *, skip_os_update: bool = False) -> dict[str, str]:
|
174
|
+
# sourcery skip: dict-assign-update-to-union
|
174
175
|
manual_env_vars = self.manual_env_vars
|
175
176
|
if manual_env_vars:
|
177
|
+
if skip_os_update:
|
178
|
+
return manual_env_vars
|
176
179
|
logger.warning(f"loading manual env-vars from {self.env_file_manual}")
|
177
180
|
os.environ.update(manual_env_vars)
|
178
181
|
else:
|
atlas_init/typer_app.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: atlas-init
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.4
|
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=2WaRI0pCJs3nKsbAgEt7upEnc9JyUA4G-ZqVJG0Jh5U,372
|
2
2
|
atlas_init/__main__.py,sha256=dY1dWWvwxRZMmnOFla6RSfti-hMeLeKdoXP7SVYqMUc,52
|
3
|
-
atlas_init/atlas_init.yaml,sha256=
|
4
|
-
atlas_init/cli.py,sha256=
|
3
|
+
atlas_init/atlas_init.yaml,sha256=OAosOZw4kjhTWcPeEv0jtztRFWRhsie8D9r5afySAxM,2065
|
4
|
+
atlas_init/cli.py,sha256=xOnAOUccHDLkivICdF0GsLhccr_IxvnTKTbe1KGW7kU,8971
|
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=nJbSuaAVhVzbxqNup1m5Nmszl2X3FOtZyI7Rjmz4JSs,2096
|
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
|
@@ -13,23 +13,25 @@ atlas_init/cli_cfn/cfn_parameter_finder.py,sha256=tAadNF1M_U2BTY-m9fXVXFXNQRvfud
|
|
13
13
|
atlas_init/cli_cfn/example.py,sha256=pQNpFreuv58O3lanLy5Kunp8GxG8i9PWjuWsYlpv2tg,8320
|
14
14
|
atlas_init/cli_cfn/files.py,sha256=vjop9G8rGMgyRe4fX5eWNX5H-YGAmk-fNUqUGErI7xg,1720
|
15
15
|
atlas_init/cli_helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
-
atlas_init/cli_helper/go.py,sha256=
|
17
|
-
atlas_init/cli_helper/run.py,sha256=
|
16
|
+
atlas_init/cli_helper/go.py,sha256=727Rh881pU5w1ZXBmDGt6ep0yvjRVpIBUL80BZyT0O8,8221
|
17
|
+
atlas_init/cli_helper/run.py,sha256=njE_ua8x_glOo6eOGa4NgZqpLcqOo3eALydrZ0bCXW4,3486
|
18
18
|
atlas_init/cli_helper/sdk.py,sha256=exh58-VZwxtosaxM269C62EEy1VnpJPOVziPDPkGsmE,2983
|
19
19
|
atlas_init/cli_helper/sdk_auto_changes.py,sha256=oWyXw7P0PdO28hclRvza_RcIVXAyzu0lCYTJTNBDMeo,189
|
20
20
|
atlas_init/cli_helper/tf_runner.py,sha256=OYdC-Y6i-xRh8_LCudKdtP7CEYEO9e67nVhholN29eg,3636
|
21
21
|
atlas_init/cli_root/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
+
atlas_init/cli_root/go_test.py,sha256=1MgBjT1qXmJAbL0Z-REm64hVN4qFLBEVTr0zwuivlyc,4265
|
22
23
|
atlas_init/cli_root/trigger.py,sha256=oEgqb_l25tyYgUaFHEuChcOCJA7k3mnRa4D-Myz-Igs,5789
|
23
24
|
atlas_init/cli_tf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
25
|
atlas_init/cli_tf/app.py,sha256=0Y5c-Pc9ibOz6kXvFlL-yhH_fx1nHLgBgK9OAVqjX9s,11390
|
25
26
|
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.py,sha256=q71ZNOnQOz1ikPCyqUz_6zyd4Bm1QVkCkTcixJBZ1xI,8988
|
27
28
|
atlas_init/cli_tf/debug_logs_test_data.py,sha256=G4pnuWJ7PAQd3NXRKAtwAPC6Ne-PgpzaTZHQ9waqxZI,9565
|
29
|
+
atlas_init/cli_tf/debug_logs_test_data_package_config.py,sha256=0GB-m8l9TWL4vstnFVO2jw5Jvtlz9WfHTp-9RmaPugw,1473
|
28
30
|
atlas_init/cli_tf/github_logs.py,sha256=VD7qhlXNuG21eTuJ5VI7rsflp5WHSodfngkRVgQlumw,8114
|
29
|
-
atlas_init/cli_tf/go_test_run.py,sha256=
|
31
|
+
atlas_init/cli_tf/go_test_run.py,sha256=LQUQ-3zJ8EUCixwu33QTAzUns3um793osst8tE0UKjk,6792
|
30
32
|
atlas_init/cli_tf/go_test_run_format.py,sha256=OUd6QPHDeTzbwVuh6MhP-xXgjOOGP9W_sCLJ8KylBTs,1201
|
31
33
|
atlas_init/cli_tf/go_test_summary.py,sha256=agr4SITgxchjgOzRpScoTUk-iG38QDLkpnsMtTW9GTY,5382
|
32
|
-
atlas_init/cli_tf/mock_tf_log.py,sha256=
|
34
|
+
atlas_init/cli_tf/mock_tf_log.py,sha256=311sUVpxbvcz6Qdpz2Z1kFQm67zfjs4aUsQOKrJ2LrY,6988
|
33
35
|
atlas_init/cli_tf/schema.py,sha256=iwvb4wD2Wba0MMu7ooTNAIi1jHbpLiXGPOT51_o_YW8,12431
|
34
36
|
atlas_init/cli_tf/schema_go_parser.py,sha256=PiRfFFVnkhltxcGFfOCgH53wwzIEynw2BXmSfaINLL8,8294
|
35
37
|
atlas_init/cli_tf/schema_inspection.py,sha256=ujLvGfg3baByND4nRD0drZoI45STxo3VfYvim-PfVOc,1764
|
@@ -54,7 +56,7 @@ atlas_init/repos/go_sdk.py,sha256=1OzM9DjHEAzAAuI9ygoRRuhUK2gqpOhXExXRqhqa0tg,17
|
|
54
56
|
atlas_init/repos/path.py,sha256=wrT8e01OBoAHj8iMrxqutgqWu-BHPe9-bEWtcZRu238,4187
|
55
57
|
atlas_init/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
58
|
atlas_init/settings/config.py,sha256=HIytZom8RRvpLGy6u8CpZ83tmFXI6v1tO3iSiuo08kc,6259
|
57
|
-
atlas_init/settings/env_vars.py,sha256=
|
59
|
+
atlas_init/settings/env_vars.py,sha256=mfpQp5Ja5tdW_mUg5Nqneb294JehQAheWNOpHvDLbg0,9844
|
58
60
|
atlas_init/settings/interactive.py,sha256=Xy1Z5WMAOSaJ-vQI_4xjAbSR92rWQgnffwVoDT27L68,340
|
59
61
|
atlas_init/settings/path.py,sha256=KkXysu6-0AuSjsvYGknYGJX1hL2j1RD-Fpf8KsVYpkE,2618
|
60
62
|
atlas_init/settings/rich_utils.py,sha256=5LgJUmc9wyJTsoS6xWKadrT0MoQREDaKvEOCuBLDXRg,1704
|
@@ -86,7 +88,7 @@ atlas_init/tf/modules/vpc_peering/vpc_peering.tf,sha256=hJ3KJdGbLpOQednUpVuiJ0Cq
|
|
86
88
|
atlas_init/tf/modules/vpc_privatelink/atlas-privatelink.tf,sha256=FloaaX1MNDvoMZxBnEopeLKyfIlq6kaX2dmx8WWlXNU,1298
|
87
89
|
atlas_init/tf/modules/vpc_privatelink/variables.tf,sha256=gktHCDYD4rz6CEpLg5aiXcFbugw4L5S2Fqc52QYdJyc,255
|
88
90
|
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.
|
91
|
+
atlas_init-0.3.4.dist-info/METADATA,sha256=bur5W1zMSSpw_8NmQhrI5Ban1vNVzmVdRu6J-tJB_co,5650
|
92
|
+
atlas_init-0.3.4.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
93
|
+
atlas_init-0.3.4.dist-info/entry_points.txt,sha256=oSNFIEAS9nUZyyZ8Fc-0F0U5j-NErygy01LpJVSHapQ,57
|
94
|
+
atlas_init-0.3.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|