atlas-init 0.1.0__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 (74) hide show
  1. atlas_init/__init__.py +3 -3
  2. atlas_init/atlas_init.yaml +51 -34
  3. atlas_init/cli.py +76 -72
  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/go.py +6 -3
  10. atlas_init/cli_helper/run.py +18 -2
  11. atlas_init/cli_helper/tf_runner.py +12 -21
  12. atlas_init/cli_root/__init__.py +0 -0
  13. atlas_init/cli_root/trigger.py +153 -0
  14. atlas_init/cli_tf/app.py +211 -4
  15. atlas_init/cli_tf/changelog.py +103 -0
  16. atlas_init/cli_tf/debug_logs.py +221 -0
  17. atlas_init/cli_tf/debug_logs_test_data.py +253 -0
  18. atlas_init/cli_tf/github_logs.py +229 -0
  19. atlas_init/cli_tf/go_test_run.py +194 -0
  20. atlas_init/cli_tf/go_test_run_format.py +31 -0
  21. atlas_init/cli_tf/go_test_summary.py +144 -0
  22. atlas_init/cli_tf/hcl/__init__.py +0 -0
  23. atlas_init/cli_tf/hcl/cli.py +161 -0
  24. atlas_init/cli_tf/hcl/cluster_mig.py +348 -0
  25. atlas_init/cli_tf/hcl/parser.py +140 -0
  26. atlas_init/cli_tf/schema.py +222 -18
  27. atlas_init/cli_tf/schema_go_parser.py +236 -0
  28. atlas_init/cli_tf/schema_table.py +150 -0
  29. atlas_init/cli_tf/schema_table_models.py +155 -0
  30. atlas_init/cli_tf/schema_v2.py +599 -0
  31. atlas_init/cli_tf/schema_v2_api_parsing.py +298 -0
  32. atlas_init/cli_tf/schema_v2_sdk.py +361 -0
  33. atlas_init/cli_tf/schema_v3.py +222 -0
  34. atlas_init/cli_tf/schema_v3_sdk.py +279 -0
  35. atlas_init/cli_tf/schema_v3_sdk_base.py +68 -0
  36. atlas_init/cli_tf/schema_v3_sdk_create.py +216 -0
  37. atlas_init/humps.py +253 -0
  38. atlas_init/repos/cfn.py +6 -1
  39. atlas_init/repos/path.py +3 -3
  40. atlas_init/settings/config.py +30 -11
  41. atlas_init/settings/env_vars.py +29 -3
  42. atlas_init/settings/path.py +12 -1
  43. atlas_init/settings/rich_utils.py +39 -2
  44. atlas_init/terraform.yaml +77 -1
  45. atlas_init/tf/.terraform.lock.hcl +125 -0
  46. atlas_init/tf/always.tf +11 -2
  47. atlas_init/tf/main.tf +3 -0
  48. atlas_init/tf/modules/aws_s3/provider.tf +1 -1
  49. atlas_init/tf/modules/aws_vars/aws_vars.tf +2 -0
  50. atlas_init/tf/modules/aws_vpc/provider.tf +4 -1
  51. atlas_init/tf/modules/cfn/cfn.tf +47 -33
  52. atlas_init/tf/modules/cfn/kms.tf +54 -0
  53. atlas_init/tf/modules/cfn/resource_actions.yaml +1 -0
  54. atlas_init/tf/modules/cfn/variables.tf +31 -0
  55. atlas_init/tf/modules/cloud_provider/cloud_provider.tf +1 -0
  56. atlas_init/tf/modules/cloud_provider/provider.tf +1 -1
  57. atlas_init/tf/modules/cluster/cluster.tf +34 -24
  58. atlas_init/tf/modules/cluster/provider.tf +1 -1
  59. atlas_init/tf/modules/federated_vars/federated_vars.tf +3 -0
  60. atlas_init/tf/modules/federated_vars/provider.tf +1 -1
  61. atlas_init/tf/modules/project_extra/project_extra.tf +15 -1
  62. atlas_init/tf/modules/stream_instance/stream_instance.tf +1 -1
  63. atlas_init/tf/modules/vpc_peering/vpc_peering.tf +1 -1
  64. atlas_init/tf/modules/vpc_privatelink/versions.tf +1 -1
  65. atlas_init/tf/outputs.tf +11 -3
  66. atlas_init/tf/providers.tf +2 -1
  67. atlas_init/tf/variables.tf +17 -0
  68. atlas_init/typer_app.py +76 -0
  69. {atlas_init-0.1.0.dist-info → atlas_init-0.1.4.dist-info}/METADATA +58 -21
  70. atlas_init-0.1.4.dist-info/RECORD +91 -0
  71. {atlas_init-0.1.0.dist-info → atlas_init-0.1.4.dist-info}/WHEEL +1 -1
  72. atlas_init-0.1.0.dist-info/RECORD +0 -61
  73. /atlas_init/tf/modules/aws_vpc/{aws-vpc.tf → aws_vpc.tf} +0 -0
  74. {atlas_init-0.1.0.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
  }
@@ -3,10 +3,12 @@ from __future__ import annotations
3
3
  import fnmatch
4
4
  import logging
5
5
  from collections.abc import Iterable
6
+ from functools import total_ordering
7
+ from os import getenv
6
8
  from pathlib import Path
7
9
  from typing import Any
8
10
 
9
- from model_lib import Entity
11
+ from model_lib import Entity, dump_ignore_falsy
10
12
  from pydantic import Field, model_validator
11
13
 
12
14
  from atlas_init.repos.path import owner_project_name
@@ -14,6 +16,7 @@ from atlas_init.repos.path import owner_project_name
14
16
  logger = logging.getLogger(__name__)
15
17
 
16
18
 
19
+ @dump_ignore_falsy
17
20
  class TerraformVars(Entity):
18
21
  cluster_info: bool = False
19
22
  cluster_info_m10: bool = False
@@ -26,8 +29,8 @@ class TerraformVars(Entity):
26
29
  use_aws_s3: bool = False
27
30
  use_federated_vars: bool = False
28
31
 
29
- def __add__(self, other: TerraformVars):
30
- assert isinstance(other, TerraformVars)
32
+ def __add__(self, other: TerraformVars): # type: ignore
33
+ assert isinstance(other, TerraformVars) # type: ignore
31
34
  kwargs = {k: v or getattr(other, k) for k, v in self}
32
35
  return type(self)(**kwargs)
33
36
 
@@ -57,10 +60,13 @@ class TerraformVars(Entity):
57
60
  if self.use_federated_vars:
58
61
  config["use_federated_vars"] = True
59
62
  if self.stream_instance:
60
- 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")}
61
65
  return config
62
66
 
63
67
 
68
+ @dump_ignore_falsy
69
+ @total_ordering
64
70
  class TestSuite(Entity):
65
71
  __test__ = False
66
72
 
@@ -68,7 +74,12 @@ class TestSuite(Entity):
68
74
  sequential_tests: bool = False
69
75
  repo_go_packages: dict[str, list[str]] = Field(default_factory=dict)
70
76
  repo_globs: dict[str, list[str]] = Field(default_factory=dict)
71
- vars: TerraformVars = Field(default_factory=TerraformVars)
77
+ vars: TerraformVars = Field(default_factory=TerraformVars) # type: ignore
78
+
79
+ def __lt__(self, other) -> bool:
80
+ if not isinstance(other, TestSuite): # type: ignore
81
+ raise TypeError
82
+ return self.name < other.name
72
83
 
73
84
  def all_globs(self, repo_alias: str) -> list[str]:
74
85
  go_packages = self.repo_go_packages.get(repo_alias, [])
@@ -95,8 +106,8 @@ class RepoAliasNotFoundError(ValueError):
95
106
 
96
107
 
97
108
  class AtlasInitConfig(Entity):
98
- test_suites: list[TestSuite] = Field(default_factory=list)
99
109
  repo_aliases: dict[str, str] = Field(default_factory=dict)
110
+ test_suites: list[TestSuite] = Field(default_factory=list) # type: ignore
100
111
 
101
112
  def repo_alias(self, repo_url_path: str) -> str:
102
113
  alias = self.repo_aliases.get(repo_url_path)
@@ -115,7 +126,7 @@ class AtlasInitConfig(Entity):
115
126
  alias: str | None,
116
127
  change_paths: Iterable[str],
117
128
  forced_test_suites: list[str],
118
- ) -> list[TestSuite]:
129
+ ) -> list[TestSuite]: # type: ignore
119
130
  forced_suites = set(forced_test_suites)
120
131
  if forced_test_suites:
121
132
  logger.warning(f"using forced test suites: {forced_test_suites}")
@@ -143,12 +154,20 @@ def active_suites(
143
154
  repo_path: Path,
144
155
  cwd_rel_path: str,
145
156
  forced_test_suites: list[str],
146
- ) -> list[TestSuite]:
157
+ ) -> list[TestSuite]: # type: ignore
147
158
  repo_url_path = owner_project_name(repo_path)
148
- repo_alias = config.repo_alias(repo_url_path)
149
- 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
+ )
150
170
  change_paths = [cwd_rel_path]
151
-
152
171
  active_suites = config.active_test_suites(repo_alias, change_paths, forced_test_suites)
153
172
  logger.info(f"active_suites: {[s.name for s in active_suites]}")
154
173
  return active_suites
@@ -47,11 +47,15 @@ class ExternalSettings(BaseSettings):
47
47
  MONGODB_ATLAS_PRIVATE_KEY: str
48
48
  MONGODB_ATLAS_PUBLIC_KEY: str
49
49
  MONGODB_ATLAS_BASE_URL: str = "https://cloud-dev.mongodb.com/"
50
- ci: bool = False
50
+ non_interactive: bool = False
51
51
 
52
52
  @property
53
53
  def is_interactive(self) -> bool:
54
- return not self.ci
54
+ return not self.non_interactive
55
+
56
+ @property
57
+ def is_mongodbgov_cloud(self) -> bool:
58
+ return "mongodbgov" in self.MONGODB_ATLAS_BASE_URL
55
59
 
56
60
 
57
61
  def as_env_var_name(field_name: str) -> str:
@@ -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"
@@ -145,6 +153,17 @@ class AtlasInitPaths(BaseSettings):
145
153
  def tf_vars_path(self) -> Path:
146
154
  return self.tf_data_dir / "vars.auto.tfvars.json"
147
155
 
156
+ @property
157
+ def tf_state_path(self) -> Path:
158
+ return self.profile_dir / "tf_state"
159
+
160
+ @property
161
+ def tf_outputs_path(self) -> Path:
162
+ return self.profile_dir / "tf_outputs.json"
163
+
164
+ def load_env_vars(self, path: Path) -> dict[str, str]:
165
+ return load_dotenv(path)
166
+
148
167
  def load_env_vars_generated(self) -> dict[str, str]:
149
168
  env_path = self.env_vars_generated
150
169
  assert env_path.exists(), f"no env-vars exist {env_path} have you forgotten apply?"
@@ -170,6 +189,7 @@ class AtlasInitSettings(AtlasInitPaths, ExternalSettings):
170
189
 
171
190
  cfn_profile: str = ""
172
191
  cfn_region: str = ""
192
+ cfn_use_kms_key: bool = False
173
193
  project_name: str = ""
174
194
 
175
195
  skip_copy: bool = False
@@ -235,7 +255,13 @@ class AtlasInitSettings(AtlasInitPaths, ExternalSettings):
235
255
 
236
256
  def cfn_config(self) -> dict[str, Any]:
237
257
  if self.cfn_profile:
238
- 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
+ }
239
265
  return {}
240
266
 
241
267