atlas-init 0.1.1__py3-none-any.whl → 0.1.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.
Files changed (73) hide show
  1. atlas_init/__init__.py +3 -3
  2. atlas_init/atlas_init.yaml +18 -1
  3. atlas_init/cli.py +62 -70
  4. atlas_init/cli_cfn/app.py +40 -117
  5. atlas_init/cli_cfn/{cfn.py → aws.py} +129 -14
  6. atlas_init/cli_cfn/cfn_parameter_finder.py +89 -6
  7. atlas_init/cli_cfn/example.py +203 -0
  8. atlas_init/cli_cfn/files.py +63 -0
  9. atlas_init/cli_helper/run.py +18 -2
  10. atlas_init/cli_helper/tf_runner.py +4 -6
  11. atlas_init/cli_root/__init__.py +0 -0
  12. atlas_init/cli_root/trigger.py +153 -0
  13. atlas_init/cli_tf/app.py +211 -4
  14. atlas_init/cli_tf/changelog.py +103 -0
  15. atlas_init/cli_tf/debug_logs.py +221 -0
  16. atlas_init/cli_tf/debug_logs_test_data.py +253 -0
  17. atlas_init/cli_tf/github_logs.py +229 -0
  18. atlas_init/cli_tf/go_test_run.py +194 -0
  19. atlas_init/cli_tf/go_test_run_format.py +31 -0
  20. atlas_init/cli_tf/go_test_summary.py +144 -0
  21. atlas_init/cli_tf/hcl/__init__.py +0 -0
  22. atlas_init/cli_tf/hcl/cli.py +161 -0
  23. atlas_init/cli_tf/hcl/cluster_mig.py +348 -0
  24. atlas_init/cli_tf/hcl/parser.py +140 -0
  25. atlas_init/cli_tf/schema.py +222 -18
  26. atlas_init/cli_tf/schema_go_parser.py +236 -0
  27. atlas_init/cli_tf/schema_table.py +150 -0
  28. atlas_init/cli_tf/schema_table_models.py +155 -0
  29. atlas_init/cli_tf/schema_v2.py +599 -0
  30. atlas_init/cli_tf/schema_v2_api_parsing.py +298 -0
  31. atlas_init/cli_tf/schema_v2_sdk.py +361 -0
  32. atlas_init/cli_tf/schema_v3.py +222 -0
  33. atlas_init/cli_tf/schema_v3_sdk.py +279 -0
  34. atlas_init/cli_tf/schema_v3_sdk_base.py +68 -0
  35. atlas_init/cli_tf/schema_v3_sdk_create.py +216 -0
  36. atlas_init/humps.py +253 -0
  37. atlas_init/repos/cfn.py +6 -1
  38. atlas_init/repos/path.py +3 -3
  39. atlas_init/settings/config.py +14 -4
  40. atlas_init/settings/env_vars.py +16 -1
  41. atlas_init/settings/path.py +12 -1
  42. atlas_init/settings/rich_utils.py +2 -0
  43. atlas_init/terraform.yaml +77 -1
  44. atlas_init/tf/.terraform.lock.hcl +59 -83
  45. atlas_init/tf/always.tf +7 -0
  46. atlas_init/tf/main.tf +3 -0
  47. atlas_init/tf/modules/aws_s3/provider.tf +1 -1
  48. atlas_init/tf/modules/aws_vars/aws_vars.tf +2 -0
  49. atlas_init/tf/modules/aws_vpc/provider.tf +4 -1
  50. atlas_init/tf/modules/cfn/cfn.tf +47 -33
  51. atlas_init/tf/modules/cfn/kms.tf +54 -0
  52. atlas_init/tf/modules/cfn/resource_actions.yaml +1 -0
  53. atlas_init/tf/modules/cfn/variables.tf +31 -0
  54. atlas_init/tf/modules/cloud_provider/cloud_provider.tf +1 -0
  55. atlas_init/tf/modules/cloud_provider/provider.tf +1 -1
  56. atlas_init/tf/modules/cluster/cluster.tf +34 -24
  57. atlas_init/tf/modules/cluster/provider.tf +1 -1
  58. atlas_init/tf/modules/federated_vars/federated_vars.tf +3 -0
  59. atlas_init/tf/modules/federated_vars/provider.tf +1 -1
  60. atlas_init/tf/modules/project_extra/project_extra.tf +15 -1
  61. atlas_init/tf/modules/stream_instance/stream_instance.tf +1 -1
  62. atlas_init/tf/modules/vpc_peering/vpc_peering.tf +1 -1
  63. atlas_init/tf/modules/vpc_privatelink/versions.tf +1 -1
  64. atlas_init/tf/outputs.tf +11 -3
  65. atlas_init/tf/providers.tf +2 -1
  66. atlas_init/tf/variables.tf +12 -0
  67. atlas_init/typer_app.py +76 -0
  68. {atlas_init-0.1.1.dist-info → atlas_init-0.1.4.dist-info}/METADATA +36 -18
  69. atlas_init-0.1.4.dist-info/RECORD +91 -0
  70. {atlas_init-0.1.1.dist-info → atlas_init-0.1.4.dist-info}/WHEEL +1 -1
  71. atlas_init-0.1.1.dist-info/RECORD +0 -62
  72. /atlas_init/tf/modules/aws_vpc/{aws-vpc.tf → aws_vpc.tf} +0 -0
  73. {atlas_init-0.1.1.dist-info → atlas_init-0.1.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,216 @@
1
+ import logging
2
+ from functools import singledispatch
3
+ from queue import Queue
4
+
5
+ from atlas_init.cli_tf.schema_v2 import (
6
+ extend_import_urls,
7
+ go_fmt,
8
+ import_lines,
9
+ package_name,
10
+ )
11
+ from atlas_init.cli_tf.schema_v2_sdk import GoVarName, SDKAttribute, SDKModel
12
+ from atlas_init.cli_tf.schema_v3 import (
13
+ TF_MODEL_NAME,
14
+ Attribute,
15
+ ComputedOptionalRequired,
16
+ ListNestedAttribute,
17
+ Resource,
18
+ SingleNestedAttribute,
19
+ )
20
+ from atlas_init.cli_tf.schema_v3_sdk_base import (
21
+ SDKAndSchemaAttribute,
22
+ find_attribute,
23
+ name_schema_struct,
24
+ name_struct_attribute,
25
+ schema_attributes,
26
+ )
27
+ from atlas_init.humps import pascalize
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ def generate_schema_to_model(resource: Resource, sdk_model: SDKModel) -> str:
33
+ func_lines = tf_to_sdk_create_func(resource, sdk_model)
34
+ import_urls = set()
35
+ extend_import_urls(import_urls, func_lines)
36
+ unformatted = "\n".join(
37
+ [
38
+ f"package {package_name(resource.name)}",
39
+ "",
40
+ *import_lines(import_urls),
41
+ "",
42
+ *func_lines,
43
+ ]
44
+ )
45
+ return go_fmt(resource.name, unformatted)
46
+
47
+
48
+ def tf_to_sdk_create_func(resource: Resource, sdk_model: SDKModel) -> list[str]:
49
+ lines = [
50
+ f"func NewAtlasReq({GoVarName.CTX} context.Context, {GoVarName.INPUT} *{TF_MODEL_NAME}, {GoVarName.DIAGS} *diag.Diagnostics) *admin.{sdk_model.name} {{"
51
+ f" return &admin.{sdk_model.name}{{"
52
+ ]
53
+ nested_attributes, call_lines = convert_or_call_lines(resource, sdk_model)
54
+ lines.extend(call_lines)
55
+ lines.extend(
56
+ [
57
+ " }" # end struct
58
+ "}" # end function
59
+ ]
60
+ )
61
+ lines.extend(process_nested_attributes(nested_attributes))
62
+ return lines
63
+
64
+
65
+ _tf_to_sdk_funcs = {
66
+ ("string", "string"): lambda tf_ref: f"{tf_ref}.ValueString()",
67
+ ("string", "*string"): lambda tf_ref: f"{tf_ref}.ValueStringPointer()",
68
+ ("*string", "*string"): lambda tf_ref: f"{tf_ref}.ValueStringPointer()",
69
+ (
70
+ "*string",
71
+ "*time.Time",
72
+ ): lambda tf_ref: f"conversion.StringPtrToTimePtr({tf_ref}.ValueStringPointer())",
73
+ ("*bool", "*bool"): lambda tf_ref: f"{tf_ref}.ValueBoolPointer()",
74
+ (
75
+ "int64",
76
+ "*int",
77
+ ): lambda tf_ref: f"conversion.Int64PtrToIntPtr({tf_ref}.ValueInt64Pointer())",
78
+ (
79
+ "*int64",
80
+ "*int",
81
+ ): lambda tf_ref: f"conversion.Int64PtrToIntPtr({tf_ref}.ValueInt64Pointer())",
82
+ ("*float64", "*float64"): lambda tf_ref: f"{tf_ref}.ValueFloat64Pointer()",
83
+ ("*int64", "*int64"): lambda tf_ref: f"{tf_ref}.ValueInt64Pointer()",
84
+ }
85
+
86
+
87
+ def tf_to_sdk_attribute_value(
88
+ schema_attribute: Attribute,
89
+ sdk_attribute: SDKAttribute,
90
+ variable_name: GoVarName = GoVarName.INPUT,
91
+ ) -> str:
92
+ key = (schema_attribute.go_type_optional, sdk_attribute.go_type)
93
+ if key in _tf_to_sdk_funcs:
94
+ return _tf_to_sdk_funcs[key](f"{variable_name}.{pascalize(schema_attribute.name)}")
95
+ raise ValueError(f"Could not find conversion function for {key} for attribute: {schema_attribute.name}")
96
+
97
+
98
+ def convert_or_call_lines(
99
+ root: Resource | Attribute,
100
+ sdk_model: SDKModel,
101
+ variable_name: GoVarName = GoVarName.INPUT,
102
+ ) -> tuple[list[SDKAndSchemaAttribute], list[str]]:
103
+ call_lines = []
104
+ nested_attributes: list[SDKAndSchemaAttribute] = []
105
+ tf_attributes = schema_attributes(root)
106
+ for sdk_attr in sorted(sdk_model.attributes.values()):
107
+ try:
108
+ tf_attribute = find_attribute(tf_attributes, sdk_attr.tf_name, root.name)
109
+ except ValueError as e:
110
+ logger.warning(e)
111
+ continue
112
+ if tf_attribute.computed_optional_required == ComputedOptionalRequired.computed:
113
+ continue
114
+ if sdk_attr.is_nested:
115
+ call_lines.append(
116
+ f" {sdk_attr.struct_name}: new{sdk_attr.struct_type_name}({GoVarName.CTX}, {variable_name}.{name_struct_attribute(tf_attribute.name)}, {GoVarName.DIAGS}),"
117
+ )
118
+ nested_attributes.append(SDKAndSchemaAttribute(sdk_attribute=sdk_attr, schema_attribute=tf_attribute))
119
+ elif tf_attribute.is_required:
120
+ call_lines.append(
121
+ f" {sdk_attr.struct_name}: {tf_to_sdk_attribute_value(tf_attribute, sdk_attr, variable_name)},"
122
+ )
123
+ else:
124
+ call_lines.append(
125
+ f" {sdk_attr.struct_name}: conversion.NilForUnknown({variable_name}.{tf_attribute.name_pascal}, {tf_to_sdk_attribute_value(tf_attribute, sdk_attr, variable_name)}),"
126
+ )
127
+ return nested_attributes, call_lines
128
+
129
+
130
+ def process_nested_attributes(
131
+ nested_attributes: list[SDKAndSchemaAttribute],
132
+ ) -> list[str]:
133
+ lines = []
134
+ queue = Queue()
135
+
136
+ def add_nested_to_queue(attributes: list[SDKAndSchemaAttribute]):
137
+ for nested in attributes:
138
+ logger.info(f"found nested attribute: {nested.schema_attribute.name}")
139
+ queue.put(nested)
140
+
141
+ add_nested_to_queue(nested_attributes)
142
+ while not queue.empty():
143
+ sdk_attribute, schema_attribute = queue.get()
144
+ more_nested_attributes, nested_lines = convert_nested_attribute(
145
+ schema_attribute.nested_model, schema_attribute, sdk_attribute
146
+ )
147
+ lines.extend(nested_lines)
148
+ add_nested_to_queue(more_nested_attributes)
149
+ return lines
150
+
151
+
152
+ @singledispatch
153
+ def convert_nested_attribute(
154
+ nested_model: object, schema_attribute: Attribute, _: SDKAttribute
155
+ ) -> tuple[list[SDKAndSchemaAttribute], list[str]]:
156
+ raise NotImplementedError(f"unsupported nested attribute: {schema_attribute.name} of type {type(nested_model)}")
157
+
158
+
159
+ @convert_nested_attribute.register
160
+ def _convert_single_nested_attribute(
161
+ _: SingleNestedAttribute,
162
+ schema_attribute: Attribute,
163
+ sdk_attribute: SDKAttribute,
164
+ ) -> tuple[list[SDKAndSchemaAttribute], list[str]]:
165
+ sdk_model = sdk_attribute.as_sdk_model()
166
+ lines: list[str] = [
167
+ f"func new{sdk_model.name}(ctx context.Context, {GoVarName.INPUT} types.Object, diags *diag.Diagnostics) *admin.{sdk_model.name} {{",
168
+ f" var resp *admin.{sdk_model.name}",
169
+ f" if {GoVarName.INPUT}.IsUnknown() || {GoVarName.INPUT}.IsNull() {{",
170
+ " return resp",
171
+ " }",
172
+ f" {GoVarName.ITEM} := &{name_schema_struct(schema_attribute.name)}{{}}",
173
+ f" if localDiags := {GoVarName.INPUT}.As({GoVarName.CTX}, {GoVarName.ITEM}, basetypes.ObjectAsOptions{{}}); len(localDiags) > 0 {{",
174
+ f" {GoVarName.DIAGS}.Append(localDiags...)",
175
+ " return resp",
176
+ " }",
177
+ f" return &admin.{sdk_model.name}{{",
178
+ ]
179
+ nested_attributes, call_lines = convert_or_call_lines(schema_attribute, sdk_model, GoVarName.ITEM)
180
+ lines.extend([*call_lines, " }", "}"]) # end struct # end function
181
+ return nested_attributes, lines
182
+
183
+
184
+ @convert_nested_attribute.register
185
+ def _convert_list_nested_attriute(
186
+ _: ListNestedAttribute,
187
+ schema_attribute: Attribute,
188
+ sdk_attribute: SDKAttribute,
189
+ ) -> tuple[list[SDKAndSchemaAttribute], list[str]]:
190
+ sdk_model = sdk_attribute.as_sdk_model()
191
+ lines: list[str] = [
192
+ f"func new{sdk_model.name}(ctx context.Context, {GoVarName.INPUT} types.List, diags *diag.Diagnostics) *[]admin.{sdk_model.name} {{",
193
+ f" if {GoVarName.INPUT}.IsUnknown() || {GoVarName.INPUT}.IsNull() {{",
194
+ " return nil",
195
+ " }",
196
+ f" {GoVarName.ELEMENTS} := make([]{name_schema_struct(schema_attribute.name)}, len({GoVarName.INPUT}.Elements()))",
197
+ f" if localDiags := {GoVarName.INPUT}.ElementsAs({GoVarName.CTX}, &{GoVarName.ELEMENTS}, false); len(localDiags) > 0 {{",
198
+ f" {GoVarName.DIAGS}.Append(localDiags...)",
199
+ " return nil",
200
+ " }",
201
+ f" {GoVarName.RESP} := make([]admin.{sdk_model.name}, len({GoVarName.INPUT}.Elements()))",
202
+ f" for i := range {GoVarName.ELEMENTS} {{",
203
+ f" {GoVarName.ITEM} := &{GoVarName.ELEMENTS}[i]",
204
+ f" resp[i] = admin.{sdk_model.name}{{",
205
+ ]
206
+ nested_attributes, call_lines = convert_or_call_lines(schema_attribute, sdk_model, GoVarName.ITEM)
207
+ lines.extend(
208
+ [
209
+ *[f" {line}" for line in call_lines],
210
+ " }", # end struct
211
+ " }", # end loop
212
+ " return &resp",
213
+ "}", # end function
214
+ ]
215
+ )
216
+ return nested_attributes, lines
atlas_init/humps.py ADDED
@@ -0,0 +1,253 @@
1
+ """
2
+ # https://raw.githubusercontent.com/nficano/humps/master/humps/main.py
3
+ This module contains all the core logic for humps.
4
+ """
5
+ import re
6
+ from collections.abc import Mapping
7
+ from typing import TypeVar # pylint: disable-msg=E0611
8
+
9
+ ACRONYM_RE = re.compile(r"([A-Z\d]+)(?=[A-Z\d]|$)")
10
+ PASCAL_RE = re.compile(r"([^\-_]+)")
11
+ SPLIT_RE = re.compile(r"([\-_]*(?<=[^0-9])(?=[A-Z])[^A-Z]*[\-_]*)")
12
+ UNDERSCORE_RE = re.compile(r"(?<=[^\-_])[\-_]+[^\-_]")
13
+ T = TypeVar("T")
14
+
15
+ def pascalize(str_or_iter: T) -> T:
16
+ """
17
+ Convert a string, dict, or list of dicts to pascal case.
18
+
19
+ :param str_or_iter:
20
+ A string or iterable.
21
+ :type str_or_iter: Union[list, dict, str]
22
+ :rtype: Union[list, dict, str]
23
+ :returns:
24
+ pascalized string, dictionary, or list of dictionaries.
25
+ """
26
+ if isinstance(str_or_iter, (list, Mapping)):
27
+ return _process_keys(str_or_iter, pascalize)
28
+
29
+ s = _is_none(str_or_iter)
30
+ if s.isupper() or s.isnumeric():
31
+ return str_or_iter
32
+
33
+ def _replace_fn(match):
34
+ """
35
+ :rtype: str
36
+ """
37
+ return match.group(1)[0].upper() + match.group(1)[1:]
38
+
39
+ s = camelize(PASCAL_RE.sub(_replace_fn, s))
40
+ return s[0].upper() + s[1:] if len(s) != 0 else s
41
+
42
+
43
+ def camelize(str_or_iter: T) -> T:
44
+ """
45
+ Convert a string, dict, or list of dicts to camel case.
46
+
47
+ :param str_or_iter:
48
+ A string or iterable.
49
+ :type str_or_iter: Union[list, dict, str]
50
+ :rtype: Union[list, dict, str]
51
+ :returns:
52
+ camelized string, dictionary, or list of dictionaries.
53
+ """
54
+ if isinstance(str_or_iter, (list, Mapping)):
55
+ return _process_keys(str_or_iter, camelize)
56
+
57
+ s = _is_none(str_or_iter)
58
+ if s.isupper() or s.isnumeric():
59
+ return str_or_iter
60
+
61
+ if len(s) != 0 and not s[:2].isupper():
62
+ s = s[0].lower() + s[1:]
63
+
64
+ # For string "hello_world", match will contain
65
+ # the regex capture group for "_w".
66
+ return UNDERSCORE_RE.sub(lambda m: m.group(0)[-1].upper(), s)
67
+
68
+
69
+ def kebabize(str_or_iter: T) -> T:
70
+ """
71
+ Convert a string, dict, or list of dicts to kebab case.
72
+ :param str_or_iter:
73
+ A string or iterable.
74
+ :type str_or_iter: Union[list, dict, str]
75
+ :rtype: Union[list, dict, str]
76
+ :returns:
77
+ kebabized string, dictionary, or list of dictionaries.
78
+ """
79
+ if isinstance(str_or_iter, (list, Mapping)):
80
+ return _process_keys(str_or_iter, kebabize)
81
+
82
+ s = _is_none(str_or_iter)
83
+ if s.isnumeric():
84
+ return str_or_iter
85
+
86
+ if not (s.isupper()) and (is_camelcase(s) or is_pascalcase(s)):
87
+ return (
88
+ _separate_words(
89
+ string=_fix_abbreviations(s),
90
+ separator="-"
91
+ ).lower()
92
+ )
93
+
94
+ return UNDERSCORE_RE.sub(lambda m: "-" + m.group(0)[-1], s)
95
+
96
+
97
+ def decamelize(str_or_iter: T) -> T:
98
+ """
99
+ Convert a string, dict, or list of dicts to snake case.
100
+
101
+ :param str_or_iter:
102
+ A string or iterable.
103
+ :type str_or_iter: Union[list, dict, str]
104
+ :rtype: Union[list, dict, str]
105
+ :returns:
106
+ snake cased string, dictionary, or list of dictionaries.
107
+ """
108
+ if isinstance(str_or_iter, (list, Mapping)):
109
+ return _process_keys(str_or_iter, decamelize)
110
+
111
+ s = _is_none(str_or_iter)
112
+ if s.isupper() or s.isnumeric():
113
+ return str_or_iter
114
+
115
+ return _separate_words(_fix_abbreviations(s)).lower()
116
+
117
+
118
+ def depascalize(str_or_iter: T) -> T:
119
+ """
120
+ Convert a string, dict, or list of dicts to snake case.
121
+
122
+ :param str_or_iter: A string or iterable.
123
+ :type str_or_iter: Union[list, dict, str]
124
+ :rtype: Union[list, dict, str]
125
+ :returns:
126
+ snake cased string, dictionary, or list of dictionaries.
127
+ """
128
+ return decamelize(str_or_iter)
129
+
130
+
131
+ def dekebabize(str_or_iter: T) -> T:
132
+ """
133
+ Convert a string, dict, or list of dicts to snake case.
134
+ :param str_or_iter:
135
+ A string or iterable.
136
+ :type str_or_iter: Union[list, dict, str]
137
+ :rtype: Union[list, dict, str]
138
+ :returns:
139
+ snake cased string, dictionary, or list of dictionaries.
140
+ """
141
+ if isinstance(str_or_iter, (list, Mapping)):
142
+ return _process_keys(str_or_iter, dekebabize)
143
+
144
+ s = _is_none(str_or_iter)
145
+ if s.isnumeric():
146
+ return str_or_iter
147
+
148
+ return s.replace("-", "_")
149
+
150
+
151
+ def is_camelcase(str_or_iter: T) -> T:
152
+ """
153
+ Determine if a string, dict, or list of dicts is camel case.
154
+
155
+ :param str_or_iter:
156
+ A string or iterable.
157
+ :type str_or_iter: Union[list, dict, str]
158
+ :rtype: bool
159
+ :returns:
160
+ True/False whether string or iterable is camel case
161
+ """
162
+ return str_or_iter == camelize(str_or_iter)
163
+
164
+
165
+ def is_pascalcase(str_or_iter: T) -> T:
166
+ """
167
+ Determine if a string, dict, or list of dicts is pascal case.
168
+
169
+ :param str_or_iter: A string or iterable.
170
+ :type str_or_iter: Union[list, dict, str]
171
+ :rtype: bool
172
+ :returns:
173
+ True/False whether string or iterable is pascal case
174
+ """
175
+ return str_or_iter == pascalize(str_or_iter)
176
+
177
+
178
+ def is_kebabcase(str_or_iter: T) -> T:
179
+ """
180
+ Determine if a string, dict, or list of dicts is camel case.
181
+ :param str_or_iter:
182
+ A string or iterable.
183
+ :type str_or_iter: Union[list, dict, str]
184
+ :rtype: bool
185
+ :returns:
186
+ True/False whether string or iterable is camel case
187
+ """
188
+ return str_or_iter == kebabize(str_or_iter)
189
+
190
+
191
+ def is_snakecase(str_or_iter: T) -> T:
192
+ """
193
+ Determine if a string, dict, or list of dicts is snake case.
194
+
195
+ :param str_or_iter:
196
+ A string or iterable.
197
+ :type str_or_iter: Union[list, dict, str]
198
+ :rtype: bool
199
+ :returns:
200
+ True/False whether string or iterable is snake case
201
+ """
202
+ if is_kebabcase(str_or_iter) and not is_camelcase(str_or_iter):
203
+ return False
204
+
205
+ return str_or_iter == decamelize(str_or_iter)
206
+
207
+
208
+ def _is_none(_in):
209
+ """
210
+ Determine if the input is None
211
+ and returns a string with white-space removed
212
+ :param _in: input
213
+ :return:
214
+ an empty sting if _in is None,
215
+ else the input is returned with white-space removed
216
+ """
217
+ return "" if _in is None else re.sub(r"\s+", "", str(_in))
218
+
219
+
220
+ def _process_keys(str_or_iter, fn):
221
+ if isinstance(str_or_iter, list):
222
+ return [_process_keys(k, fn) for k in str_or_iter]
223
+ if isinstance(str_or_iter, Mapping):
224
+ return {fn(k): _process_keys(v, fn) for k, v in str_or_iter.items()}
225
+ return str_or_iter
226
+
227
+
228
+ def _fix_abbreviations(string):
229
+ """
230
+ Rewrite incorrectly cased acronyms, initialisms, and abbreviations,
231
+ allowing them to be decamelized correctly. For example, given the string
232
+ "APIResponse", this function is responsible for ensuring the output is
233
+ "api_response" instead of "a_p_i_response".
234
+
235
+ :param string: A string that may contain an incorrectly cased abbreviation.
236
+ :type string: str
237
+ :rtype: str
238
+ :returns:
239
+ A rewritten string that is safe for decamelization.
240
+ """
241
+ return ACRONYM_RE.sub(lambda m: m.group(0).title(), string)
242
+
243
+
244
+ def _separate_words(string, separator="_"):
245
+ """
246
+ Split words that are separated by case differentiation.
247
+ :param string: Original string.
248
+ :param separator: String by which the individual
249
+ words will be put back together.
250
+ :returns:
251
+ New string.
252
+ """
253
+ return separator.join(s for s in SPLIT_RE.split(string) if s)
atlas_init/repos/cfn.py CHANGED
@@ -9,6 +9,7 @@ from zero_3rdparty.dict_nested import read_nested_or_none
9
9
  from zero_3rdparty.str_utils import ensure_prefix
10
10
 
11
11
  from atlas_init.cloud.aws import REGIONS, AwsRegion
12
+ from atlas_init.humps import pascalize
12
13
  from atlas_init.repos.path import current_dir
13
14
 
14
15
  logger = logging.getLogger(__name__)
@@ -41,9 +42,13 @@ class CfnType(Entity):
41
42
 
42
43
  @model_validator(mode="after")
43
44
  def ensure_type_name_prefix(self):
44
- self.type_name = ensure_prefix(self.type_name.capitalize(), self.MONGODB_ATLAS_CFN_TYPE_PREFIX)
45
+ self.type_name = ensure_prefix(pascalize(self.type_name), self.MONGODB_ATLAS_CFN_TYPE_PREFIX)
45
46
  return self
46
47
 
48
+ @classmethod
49
+ def resource_name(cls, type_name: str) -> str:
50
+ return type_name.removeprefix(cls.MONGODB_ATLAS_CFN_TYPE_PREFIX).lower()
51
+
47
52
 
48
53
  class Operation(StrEnum):
49
54
  DELETE = "delete"
atlas_init/repos/path.py CHANGED
@@ -29,11 +29,11 @@ _resource_roots = {
29
29
  }
30
30
 
31
31
 
32
- def _default_is_resource(_: Path) -> Callable[[Path], bool]:
33
- raise NotImplementedError
32
+ def _default_is_resource(p: Path) -> bool:
33
+ return "internal/service" in str(p)
34
34
 
35
35
 
36
- _resource_is_resource = {
36
+ _resource_is_resource: dict[str, Callable[[Path], bool]] = {
37
37
  GH_OWNER_MONGODBATLAS_CLOUDFORMATION_RESOURCES: lambda p: (p / "cmd/main.go").exists(),
38
38
  GH_OWNER_TERRAFORM_PROVIDER_MONGODBATLAS: _default_is_resource,
39
39
  }
@@ -4,6 +4,7 @@ import fnmatch
4
4
  import logging
5
5
  from collections.abc import Iterable
6
6
  from functools import total_ordering
7
+ from os import getenv
7
8
  from pathlib import Path
8
9
  from typing import Any
9
10
 
@@ -59,7 +60,8 @@ class TerraformVars(Entity):
59
60
  if self.use_federated_vars:
60
61
  config["use_federated_vars"] = True
61
62
  if self.stream_instance:
62
- config["stream_instance_config"] = {"name": "atlas-init"}
63
+ # hack until backend bug with stream instance is fixed
64
+ config["stream_instance_config"] = {"name": getenv("ATLAS_STREAM_INSTANCE_NAME", "atlas-init")}
63
65
  return config
64
66
 
65
67
 
@@ -154,10 +156,18 @@ def active_suites(
154
156
  forced_test_suites: list[str],
155
157
  ) -> list[TestSuite]: # type: ignore
156
158
  repo_url_path = owner_project_name(repo_path)
157
- repo_alias = config.repo_alias(repo_url_path)
158
- logger.info(f"repo_alias={repo_alias}, repo_path={repo_path}, repo_url_path={repo_url_path}")
159
+ try:
160
+ repo_alias = config.repo_alias(repo_url_path)
161
+ except RepoAliasNotFoundError:
162
+ if forced_test_suites:
163
+ # still want to use the forced test suites
164
+ repo_alias = None
165
+ else:
166
+ raise
167
+ logger.info(
168
+ f"repo_alias={repo_alias}, repo_path={repo_path}, repo_url_path={repo_url_path}, cwd_rel_path={cwd_rel_path}"
169
+ )
159
170
  change_paths = [cwd_rel_path]
160
-
161
171
  active_suites = config.active_test_suites(repo_alias, change_paths, forced_test_suites)
162
172
  logger.info(f"active_suites: {[s.name for s in active_suites]}")
163
173
  return active_suites
@@ -53,6 +53,10 @@ class ExternalSettings(BaseSettings):
53
53
  def is_interactive(self) -> bool:
54
54
  return not self.non_interactive
55
55
 
56
+ @property
57
+ def is_mongodbgov_cloud(self) -> bool:
58
+ return "mongodbgov" in self.MONGODB_ATLAS_BASE_URL
59
+
56
60
 
57
61
  def as_env_var_name(field_name: str) -> str:
58
62
  names = set(field_names(AtlasInitSettings))
@@ -137,6 +141,10 @@ class AtlasInitPaths(BaseSettings):
137
141
  def env_vars_vs_code(self) -> Path:
138
142
  return self.profile_dir / ".env-vscode"
139
143
 
144
+ @property
145
+ def env_vars_trigger(self) -> Path:
146
+ return self.profile_dir / ".env-trigger"
147
+
140
148
  @property
141
149
  def tf_data_dir(self) -> Path:
142
150
  return self.profile_dir / ".terraform"
@@ -181,6 +189,7 @@ class AtlasInitSettings(AtlasInitPaths, ExternalSettings):
181
189
 
182
190
  cfn_profile: str = ""
183
191
  cfn_region: str = ""
192
+ cfn_use_kms_key: bool = False
184
193
  project_name: str = ""
185
194
 
186
195
  skip_copy: bool = False
@@ -246,7 +255,13 @@ class AtlasInitSettings(AtlasInitPaths, ExternalSettings):
246
255
 
247
256
  def cfn_config(self) -> dict[str, Any]:
248
257
  if self.cfn_profile:
249
- return {"cfn_config": {"profile": self.cfn_profile, "region": self.cfn_region}}
258
+ return {
259
+ "cfn_config": {
260
+ "profile": self.cfn_profile,
261
+ "region": self.cfn_region,
262
+ "use_kms_key": self.cfn_use_kms_key,
263
+ }
264
+ }
250
265
  return {}
251
266
 
252
267
 
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from collections.abc import Callable
2
3
  from pathlib import Path
3
4
 
4
5
  import dotenv
@@ -21,15 +22,18 @@ DEFAULT_PROFILES_PATH.mkdir(exist_ok=True, parents=True)
21
22
  DEFAULT_TF_PATH = ROOT_PATH / "tf"
22
23
  DEFAULT_CONFIG_PATH = ROOT_PATH / "atlas_init.yaml"
23
24
  DEFAULT_SCHEMA_CONFIG_PATH = ROOT_PATH / "terraform.yaml"
25
+ DEFAULT_GITHUB_CI_RUN_LOGS = ROOT_PATH / "github_ci_run_logs"
26
+ DEFAULT_GITHUB_SUMMARY_DIR = ROOT_PATH / "github_ci_summary"
24
27
 
25
28
 
26
29
  def load_dotenv(env_path: Path) -> dict[str, str]:
27
30
  return {k: v for k, v in dotenv.dotenv_values(env_path).items() if v}
28
31
 
29
32
 
30
- def dump_vscode_dotenv(generated_path: Path, vscode_env_path: Path) -> None:
33
+ def dump_vscode_dotenv(generated_path: Path, vscode_env_path: Path, **extras: str) -> None:
31
34
  vscode_env_vars = load_dotenv(generated_path)
32
35
  vscode_env_vars.pop("TF_CLI_CONFIG_FILE", None) # migration tests will use local provider instead of online
36
+ vscode_env_vars.update(extras)
33
37
  dump_dotenv(vscode_env_path, vscode_env_vars)
34
38
 
35
39
 
@@ -43,6 +47,13 @@ def current_dir():
43
47
  return Path(os.path.curdir).absolute()
44
48
 
45
49
 
50
+ def default_factory_cwd(rel_path: str) -> Callable[[], Path]:
51
+ def default_factory():
52
+ return current_dir() / rel_path
53
+
54
+ return default_factory
55
+
56
+
46
57
  def repo_path_rel_path() -> tuple[Path, str]:
47
58
  cwd = current_dir()
48
59
  rel_path = []
@@ -35,6 +35,8 @@ def hide_secrets(handler: logging.Handler, secrets_dict: dict[str, str]) -> None
35
35
  if not isinstance(value, str):
36
36
  continue
37
37
  key_lower = key.lower()
38
+ if key_lower in {"true", "false"}:
39
+ continue
38
40
  if any(safe in key_lower for safe in safe_keys):
39
41
  continue
40
42
  if any(danger in key_lower for danger in dangerous_keys):