atlas-init 0.6.0__py3-none-any.whl → 0.7.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/cli_args.py +19 -1
- atlas_init/cli_tf/ci_tests.py +116 -24
- 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/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 +13 -1
- 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 +17 -0
- atlas_init/tf_ext/constants.py +3 -0
- atlas_init/tf_ext/models.py +106 -0
- atlas_init/tf_ext/paths.py +126 -0
- atlas_init/tf_ext/settings.py +39 -0
- atlas_init/tf_ext/tf_dep.py +324 -0
- atlas_init/tf_ext/tf_modules.py +394 -0
- atlas_init/tf_ext/tf_vars.py +173 -0
- atlas_init/tf_ext/typer_app.py +24 -0
- {atlas_init-0.6.0.dist-info → atlas_init-0.7.0.dist-info}/METADATA +3 -2
- {atlas_init-0.6.0.dist-info → atlas_init-0.7.0.dist-info}/RECORD +45 -28
- atlas_init-0.7.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.7.0.dist-info}/WHEEL +0 -0
- {atlas_init-0.6.0.dist-info → atlas_init-0.7.0.dist-info}/licenses/LICENSE +0 -0
atlas_init/__init__.py
CHANGED
atlas_init/cli_args.py
CHANGED
@@ -1,10 +1,28 @@
|
|
1
|
+
from __future__ import annotations
|
1
2
|
from typing import Any
|
2
3
|
|
4
|
+
from pydantic import BaseModel, DirectoryPath
|
3
5
|
import typer
|
4
6
|
from model_lib import parse_payload
|
5
7
|
from zero_3rdparty.iter_utils import key_equal_value_to_dict
|
6
8
|
|
7
|
-
|
9
|
+
ENV_VAR_SDK_REPO_PATH = "SDK_REPO_PATH"
|
10
|
+
option_sdk_repo_path = typer.Option(
|
11
|
+
"", "-sdk", "--sdk-repo-path", help="the path to the sdk repo", envvar=ENV_VAR_SDK_REPO_PATH
|
12
|
+
)
|
13
|
+
option_mms_repo_path = typer.Option("", "--mms-path", help="Path to the mms directory", envvar="MMS_PATH")
|
14
|
+
|
15
|
+
|
16
|
+
class ParsedPaths(BaseModel):
|
17
|
+
sdk_repo_path: DirectoryPath | None = None
|
18
|
+
mms_repo_path: DirectoryPath | None = None
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
def from_strings(cls, *, sdk_repo_path_str: str = "", mms_path: str = "") -> ParsedPaths:
|
22
|
+
return cls(
|
23
|
+
sdk_repo_path=sdk_repo_path_str or None, # type: ignore
|
24
|
+
mms_repo_path=mms_path or None, # type: ignore
|
25
|
+
)
|
8
26
|
|
9
27
|
|
10
28
|
def parse_key_values(params: list[str]) -> dict[str, str]:
|
atlas_init/cli_tf/ci_tests.py
CHANGED
@@ -9,15 +9,16 @@ from datetime import date, datetime, timedelta
|
|
9
9
|
from pathlib import Path
|
10
10
|
|
11
11
|
import typer
|
12
|
-
from ask_shell import confirm,
|
13
|
-
from model_lib import Entity, Event
|
12
|
+
from ask_shell import confirm, new_task, print_to_live, run_and_wait, select_list
|
13
|
+
from model_lib import Entity, Event, copy_and_validate
|
14
14
|
from pydantic import Field, ValidationError, field_validator, model_validator
|
15
15
|
from pydantic_core import Url
|
16
16
|
from rich.markdown import Markdown
|
17
|
-
from zero_3rdparty import file_utils
|
17
|
+
from zero_3rdparty import file_utils
|
18
18
|
from zero_3rdparty.datetime_utils import utc_now
|
19
|
+
from zero_3rdparty.str_utils import ensure_suffix
|
19
20
|
|
20
|
-
from atlas_init.cli_helper.run import add_to_clipboard
|
21
|
+
from atlas_init.cli_helper.run import add_to_clipboard
|
21
22
|
from atlas_init.cli_tf.github_logs import (
|
22
23
|
GH_TOKEN_ENV_NAME,
|
23
24
|
download_job_safely,
|
@@ -25,7 +26,17 @@ from atlas_init.cli_tf.github_logs import (
|
|
25
26
|
tf_repo,
|
26
27
|
)
|
27
28
|
from atlas_init.cli_tf.go_test_run import GoTestRun, GoTestStatus, parse_tests
|
28
|
-
from atlas_init.cli_tf.go_test_summary import
|
29
|
+
from atlas_init.cli_tf.go_test_summary import (
|
30
|
+
DailyReportIn,
|
31
|
+
DailyReportOut,
|
32
|
+
ErrorRowColumns,
|
33
|
+
MonthlyReportIn,
|
34
|
+
RunHistoryFilter,
|
35
|
+
TFCITestOutput,
|
36
|
+
TestRow,
|
37
|
+
create_daily_report,
|
38
|
+
create_monthly_report,
|
39
|
+
)
|
29
40
|
from atlas_init.cli_tf.go_test_tf_error import (
|
30
41
|
DetailsInfo,
|
31
42
|
ErrorClassAuthor,
|
@@ -40,6 +51,7 @@ from atlas_init.crud.mongo_dao import (
|
|
40
51
|
init_mongo_dao,
|
41
52
|
read_tf_resources,
|
42
53
|
)
|
54
|
+
from atlas_init.html_out.md_export import MonthlyReportPaths, export_ci_tests_markdown_to_html
|
43
55
|
from atlas_init.repos.go_sdk import ApiSpecPaths, parse_api_spec_paths
|
44
56
|
from atlas_init.repos.path import Repo, current_repo_path
|
45
57
|
from atlas_init.settings.env_vars import AtlasInitSettings, init_settings
|
@@ -73,7 +85,9 @@ class TFCITestInput(Event):
|
|
73
85
|
|
74
86
|
def ci_tests(
|
75
87
|
test_group_name: str = typer.Option("", "-g"),
|
76
|
-
max_days_ago: int = typer.Option(
|
88
|
+
max_days_ago: int = typer.Option(
|
89
|
+
1, "-d", "--days", help="number of days to look back, Github only store logs for 30 days."
|
90
|
+
),
|
77
91
|
branch: str = typer.Option("master", "-b", "--branch"),
|
78
92
|
workflow_file_stems: str = typer.Option("test-suite,terraform-compatibility-matrix", "-w", "--workflow"),
|
79
93
|
names: str = typer.Option(
|
@@ -83,16 +97,20 @@ def ci_tests(
|
|
83
97
|
help="comma separated list of test names to filter, e.g., TestAccCloudProviderAccessAuthorizationAzure_basic,TestAccBackupSnapshotExportBucket_basicAzure",
|
84
98
|
),
|
85
99
|
summary_name: str = typer.Option(
|
86
|
-
|
100
|
+
...,
|
87
101
|
"-s",
|
88
102
|
"--summary",
|
89
103
|
help="the name of the summary directory to store detailed test results",
|
104
|
+
default_factory=lambda: utc_now().strftime("%Y-%m-%d"),
|
90
105
|
),
|
91
106
|
summary_env_name: str = typer.Option("", "--env", help="filter summary based on tests/errors only in dev/qa"),
|
92
107
|
skip_log_download: bool = typer.Option(False, "-sld", "--skip-log-download", help="skip downloading logs"),
|
93
108
|
skip_error_parsing: bool = typer.Option(
|
94
109
|
False, "-sep", "--skip-error-parsing", help="skip parsing errors, usually together with --skip-log-download"
|
95
110
|
),
|
111
|
+
skip_daily: bool = typer.Option(False, "-sd", "--skip-daily", help="skip daily report"),
|
112
|
+
skip_monthly: bool = typer.Option(False, "-sm", "--skip-monthly", help="skip monthly report"),
|
113
|
+
ask_to_open: bool = typer.Option(False, "--open", "--ask-to-open", help="ask to open the reports"),
|
96
114
|
copy_to_clipboard: bool = typer.Option(
|
97
115
|
False,
|
98
116
|
"--copy",
|
@@ -111,8 +129,6 @@ def ci_tests(
|
|
111
129
|
logger.info(f"filtering tests by names: {names_set} (todo: support this)")
|
112
130
|
if test_group_name:
|
113
131
|
logger.warning(f"test_group_name is not supported yet: {test_group_name}")
|
114
|
-
if summary_name:
|
115
|
-
logger.warning(f"summary_name is not supported yet: {summary_name}")
|
116
132
|
event = TFCITestInput(
|
117
133
|
test_group_name=test_group_name,
|
118
134
|
max_days_ago=max_days_ago,
|
@@ -124,29 +140,98 @@ def ci_tests(
|
|
124
140
|
skip_log_download=skip_log_download,
|
125
141
|
skip_error_parsing=skip_error_parsing,
|
126
142
|
)
|
127
|
-
|
143
|
+
history_filter = RunHistoryFilter(
|
144
|
+
run_history_start=event.report_date - timedelta(days=event.max_days_ago),
|
145
|
+
run_history_end=event.report_date,
|
146
|
+
env_filter=[summary_env_name] if summary_env_name else [],
|
147
|
+
)
|
128
148
|
settings = event.settings
|
149
|
+
report_paths = MonthlyReportPaths.from_settings(settings, summary_name)
|
150
|
+
if skip_daily:
|
151
|
+
logger.info("skipping daily report")
|
152
|
+
else:
|
153
|
+
run_daily_report(event, settings, history_filter, copy_to_clipboard, report_paths)
|
154
|
+
if summary_name.lower() != "none":
|
155
|
+
monthly_input = MonthlyReportIn(
|
156
|
+
name=summary_name,
|
157
|
+
branch=event.branch,
|
158
|
+
history_filter=history_filter,
|
159
|
+
report_paths=report_paths,
|
160
|
+
)
|
161
|
+
if skip_monthly:
|
162
|
+
logger.info("skipping monthly report")
|
163
|
+
else:
|
164
|
+
generate_monthly_summary(settings, monthly_input, ask_to_open)
|
165
|
+
export_ci_tests_markdown_to_html(settings, report_paths)
|
166
|
+
|
167
|
+
|
168
|
+
def run_daily_report(
|
169
|
+
event: TFCITestInput,
|
170
|
+
settings: AtlasInitSettings,
|
171
|
+
history_filter: RunHistoryFilter,
|
172
|
+
copy_to_clipboard: bool,
|
173
|
+
report_paths: MonthlyReportPaths,
|
174
|
+
) -> DailyReportOut:
|
175
|
+
out = asyncio.run(ci_tests_pipeline(event))
|
129
176
|
manual_classification(out.classified_errors, settings)
|
177
|
+
summary_name = event.summary_name
|
178
|
+
|
179
|
+
def add_md_link(row: TestRow, row_dict: dict[str, str]) -> dict[str, str]:
|
180
|
+
if not summary_name:
|
181
|
+
return row_dict
|
182
|
+
old_details = row_dict[ErrorRowColumns.DETAILS_SUMMARY]
|
183
|
+
old_details = old_details or "Test History"
|
184
|
+
row_dict[ErrorRowColumns.DETAILS_SUMMARY] = (
|
185
|
+
f"[{old_details}]({settings.github_ci_summary_details_rel_path(summary_name, row.full_name)})"
|
186
|
+
)
|
187
|
+
return row_dict
|
188
|
+
|
130
189
|
daily_in = DailyReportIn(
|
131
190
|
report_date=event.report_date,
|
132
|
-
|
133
|
-
|
134
|
-
env_filter=[summary_env_name] if summary_env_name else [],
|
191
|
+
history_filter=history_filter,
|
192
|
+
row_modifier=add_md_link,
|
135
193
|
)
|
136
194
|
daily_out = create_daily_report(out, settings, daily_in)
|
137
195
|
print_to_live(Markdown(daily_out.summary_md))
|
138
196
|
if copy_to_clipboard:
|
139
197
|
add_to_clipboard(daily_out.summary_md, logger=logger)
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
198
|
+
file_utils.ensure_parents_write_text(report_paths.daily_path, daily_out.summary_md)
|
199
|
+
return daily_out
|
200
|
+
|
201
|
+
|
202
|
+
def generate_monthly_summary(
|
203
|
+
settings: AtlasInitSettings, monthly_input: MonthlyReportIn, ask_to_open: bool = False
|
204
|
+
) -> None:
|
205
|
+
monthly_out = create_monthly_report(
|
206
|
+
settings,
|
207
|
+
monthly_input,
|
208
|
+
)
|
209
|
+
paths = monthly_input.report_paths
|
210
|
+
summary_path = paths.summary_path
|
211
|
+
file_utils.ensure_parents_write_text(summary_path, monthly_out.summary_md)
|
212
|
+
logger.info(f"summary written to {summary_path}")
|
213
|
+
details_dir = paths.details_dir
|
214
|
+
file_utils.clean_dir(details_dir, recreate=True)
|
215
|
+
logger.info(f"Writing details to {details_dir}")
|
216
|
+
for name, details_md in monthly_out.test_details_md.items():
|
217
|
+
details_path = details_dir / ensure_suffix(name, ".md")
|
218
|
+
file_utils.ensure_parents_write_text(details_path, details_md)
|
219
|
+
monthly_error_only_out = create_monthly_report(
|
220
|
+
settings,
|
221
|
+
event=copy_and_validate(
|
222
|
+
monthly_input,
|
223
|
+
skip_rows=[MonthlyReportIn.skip_if_no_failures],
|
224
|
+
existing_details_md=monthly_out.test_details_md,
|
225
|
+
),
|
226
|
+
)
|
227
|
+
error_only_path = paths.error_only_path
|
228
|
+
file_utils.ensure_parents_write_text(error_only_path, monthly_error_only_out.summary_md)
|
229
|
+
logger.info(f"error-only summary written to {error_only_path}")
|
230
|
+
if ask_to_open and confirm(f"do you want to open the summary file? {summary_path}", default=False):
|
231
|
+
run_and_wait(f'code "{summary_path}"')
|
232
|
+
if ask_to_open and confirm(f"do you want to open the error-only summary file? {error_only_path}", default=False):
|
233
|
+
run_and_wait(f'code "{error_only_path}"')
|
234
|
+
return None
|
150
235
|
|
151
236
|
|
152
237
|
async def ci_tests_pipeline(event: TFCITestInput) -> TFCITestOutput:
|
@@ -290,7 +375,7 @@ def add_llm_classifications(needs_classification_errors: list[GoTestError]) -> l
|
|
290
375
|
]
|
291
376
|
|
292
377
|
|
293
|
-
class DownloadJobLogsInput(
|
378
|
+
class DownloadJobLogsInput(Entity):
|
294
379
|
branch: str = "master"
|
295
380
|
workflow_file_stems: set[str] = Field(default_factory=lambda: set(_TEST_STEMS))
|
296
381
|
max_days_ago: int = 1
|
@@ -301,6 +386,13 @@ class DownloadJobLogsInput(Event):
|
|
301
386
|
def start_date(self) -> datetime:
|
302
387
|
return self.end_date - timedelta(days=self.max_days_ago)
|
303
388
|
|
389
|
+
@model_validator(mode="after")
|
390
|
+
def check_max_days_ago(self) -> DownloadJobLogsInput:
|
391
|
+
if self.max_days_ago > 90:
|
392
|
+
logger.warning(f"max_days_ago for {type(self).__name__} must be less than or equal to 90, setting to 90")
|
393
|
+
self.max_days_ago = 90
|
394
|
+
return self
|
395
|
+
|
304
396
|
|
305
397
|
def download_logs(event: DownloadJobLogsInput, settings: AtlasInitSettings) -> list[Path]:
|
306
398
|
token = run_and_wait("gh auth token", cwd=event.repo_path).stdout
|
atlas_init/cli_tf/go_test_run.py
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from collections import defaultdict, deque
|
4
|
-
from functools import total_ordering
|
5
3
|
import logging
|
6
4
|
import re
|
5
|
+
from collections import defaultdict, deque
|
7
6
|
from collections.abc import Callable
|
8
7
|
from dataclasses import dataclass, field
|
9
8
|
from datetime import datetime
|
10
9
|
from enum import StrEnum
|
10
|
+
from functools import total_ordering
|
11
11
|
from pathlib import Path
|
12
12
|
from typing import NamedTuple, TypeAlias
|
13
13
|
|
@@ -162,6 +162,10 @@ class GoTestRun(Entity):
|
|
162
162
|
def is_failure(self) -> bool:
|
163
163
|
return self.status in {GoTestStatus.FAIL, GoTestStatus.TIMEOUT}
|
164
164
|
|
165
|
+
@property
|
166
|
+
def is_skipped(self) -> bool:
|
167
|
+
return self.status == GoTestStatus.SKIP
|
168
|
+
|
165
169
|
@property
|
166
170
|
def is_pass(self) -> bool:
|
167
171
|
return self.status == GoTestStatus.PASS
|
@@ -182,6 +186,14 @@ class GoTestRun(Entity):
|
|
182
186
|
return f"{self.package_url.split('/')[-1]}/{self.name}"
|
183
187
|
return self.name
|
184
188
|
|
189
|
+
@property
|
190
|
+
def full_name(self) -> str:
|
191
|
+
if self.package_url and self.group_name:
|
192
|
+
return f"{self.group_name}/{self.package_url.split('/')[-1]}/{self.name}"
|
193
|
+
if self.group_name:
|
194
|
+
return f"{self.group_name}/{self.name}"
|
195
|
+
return self.name_with_package
|
196
|
+
|
185
197
|
@classmethod
|
186
198
|
def group_by_name_package(cls, tests: list[GoTestRun]) -> dict[str, list[GoTestRun]]:
|
187
199
|
grouped = defaultdict(list)
|