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.
Files changed (46) hide show
  1. atlas_init/__init__.py +1 -1
  2. atlas_init/cli_args.py +19 -1
  3. atlas_init/cli_tf/ci_tests.py +116 -24
  4. atlas_init/cli_tf/go_test_run.py +14 -2
  5. atlas_init/cli_tf/go_test_summary.py +334 -82
  6. atlas_init/cli_tf/go_test_tf_error.py +20 -12
  7. atlas_init/cli_tf/hcl/modifier2.py +120 -0
  8. atlas_init/cli_tf/openapi.py +10 -6
  9. atlas_init/html_out/__init__.py +0 -0
  10. atlas_init/html_out/md_export.py +143 -0
  11. atlas_init/sdk_ext/__init__.py +0 -0
  12. atlas_init/sdk_ext/go.py +102 -0
  13. atlas_init/sdk_ext/typer_app.py +18 -0
  14. atlas_init/settings/env_vars.py +13 -1
  15. atlas_init/settings/env_vars_generated.py +2 -0
  16. atlas_init/tf/.terraform.lock.hcl +33 -33
  17. atlas_init/tf/modules/aws_s3/provider.tf +1 -1
  18. atlas_init/tf/modules/aws_vpc/provider.tf +1 -1
  19. atlas_init/tf/modules/cloud_provider/provider.tf +1 -1
  20. atlas_init/tf/modules/cluster/provider.tf +1 -1
  21. atlas_init/tf/modules/encryption_at_rest/provider.tf +1 -1
  22. atlas_init/tf/modules/federated_vars/federated_vars.tf +1 -2
  23. atlas_init/tf/modules/federated_vars/provider.tf +1 -1
  24. atlas_init/tf/modules/project_extra/provider.tf +1 -1
  25. atlas_init/tf/modules/stream_instance/provider.tf +1 -1
  26. atlas_init/tf/modules/vpc_peering/provider.tf +1 -1
  27. atlas_init/tf/modules/vpc_privatelink/versions.tf +1 -1
  28. atlas_init/tf/providers.tf +1 -1
  29. atlas_init/tf_ext/__init__.py +0 -0
  30. atlas_init/tf_ext/__main__.py +3 -0
  31. atlas_init/tf_ext/api_call.py +325 -0
  32. atlas_init/tf_ext/args.py +17 -0
  33. atlas_init/tf_ext/constants.py +3 -0
  34. atlas_init/tf_ext/models.py +106 -0
  35. atlas_init/tf_ext/paths.py +126 -0
  36. atlas_init/tf_ext/settings.py +39 -0
  37. atlas_init/tf_ext/tf_dep.py +324 -0
  38. atlas_init/tf_ext/tf_modules.py +394 -0
  39. atlas_init/tf_ext/tf_vars.py +173 -0
  40. atlas_init/tf_ext/typer_app.py +24 -0
  41. {atlas_init-0.6.0.dist-info → atlas_init-0.7.0.dist-info}/METADATA +3 -2
  42. {atlas_init-0.6.0.dist-info → atlas_init-0.7.0.dist-info}/RECORD +45 -28
  43. atlas_init-0.7.0.dist-info/entry_points.txt +5 -0
  44. atlas_init-0.6.0.dist-info/entry_points.txt +0 -2
  45. {atlas_init-0.6.0.dist-info → atlas_init-0.7.0.dist-info}/WHEEL +0 -0
  46. {atlas_init-0.6.0.dist-info → atlas_init-0.7.0.dist-info}/licenses/LICENSE +0 -0
atlas_init/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- VERSION = "0.6.0"
3
+ VERSION = "0.7.0"
4
4
 
5
5
 
6
6
  def running_in_repo() -> bool:
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
- option_sdk_repo_path = typer.Option("", "-sdk", "--sdk-repo-path", help="the path to the sdk repo")
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]:
@@ -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, select_list, print_to_live, new_task, run_and_wait
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, str_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, run_command_receive_result
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 DailyReportIn, TFCITestOutput, create_daily_report
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(1, "-d", "--days"),
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
- out = asyncio.run(ci_tests_pipeline(event))
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
- run_history_start=event.report_date - timedelta(days=event.max_days_ago),
133
- run_history_end=event.report_date,
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
- if summary_name:
141
- summary_path = settings.github_ci_summary_dir / str_utils.ensure_suffix(summary_name, ".md")
142
- file_utils.ensure_parents_write_text(summary_path, daily_out.summary_md)
143
- logger.info(f"summary written to {summary_path}")
144
- if report_details_md := daily_out.details_md:
145
- details_path = summary_path.with_name(f"{summary_path.stem}_details.md")
146
- file_utils.ensure_parents_write_text(details_path, report_details_md)
147
- logger.info(f"summary details written to {details_path}")
148
- if confirm(f"do you want to open the summary file? {summary_path}", default=False):
149
- run_command_receive_result(f'code "{summary_path}"', cwd=event.repo_path, logger=logger)
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(Event):
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
@@ -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)