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.
- atlas_init/__init__.py +3 -3
- atlas_init/atlas_init.yaml +51 -34
- atlas_init/cli.py +76 -72
- atlas_init/cli_cfn/app.py +40 -117
- atlas_init/cli_cfn/{cfn.py → aws.py} +129 -14
- atlas_init/cli_cfn/cfn_parameter_finder.py +89 -6
- atlas_init/cli_cfn/example.py +203 -0
- atlas_init/cli_cfn/files.py +63 -0
- atlas_init/cli_helper/go.py +6 -3
- atlas_init/cli_helper/run.py +18 -2
- atlas_init/cli_helper/tf_runner.py +12 -21
- atlas_init/cli_root/__init__.py +0 -0
- atlas_init/cli_root/trigger.py +153 -0
- atlas_init/cli_tf/app.py +211 -4
- atlas_init/cli_tf/changelog.py +103 -0
- atlas_init/cli_tf/debug_logs.py +221 -0
- atlas_init/cli_tf/debug_logs_test_data.py +253 -0
- atlas_init/cli_tf/github_logs.py +229 -0
- atlas_init/cli_tf/go_test_run.py +194 -0
- atlas_init/cli_tf/go_test_run_format.py +31 -0
- atlas_init/cli_tf/go_test_summary.py +144 -0
- atlas_init/cli_tf/hcl/__init__.py +0 -0
- atlas_init/cli_tf/hcl/cli.py +161 -0
- atlas_init/cli_tf/hcl/cluster_mig.py +348 -0
- atlas_init/cli_tf/hcl/parser.py +140 -0
- atlas_init/cli_tf/schema.py +222 -18
- atlas_init/cli_tf/schema_go_parser.py +236 -0
- atlas_init/cli_tf/schema_table.py +150 -0
- atlas_init/cli_tf/schema_table_models.py +155 -0
- atlas_init/cli_tf/schema_v2.py +599 -0
- atlas_init/cli_tf/schema_v2_api_parsing.py +298 -0
- atlas_init/cli_tf/schema_v2_sdk.py +361 -0
- atlas_init/cli_tf/schema_v3.py +222 -0
- atlas_init/cli_tf/schema_v3_sdk.py +279 -0
- atlas_init/cli_tf/schema_v3_sdk_base.py +68 -0
- atlas_init/cli_tf/schema_v3_sdk_create.py +216 -0
- atlas_init/humps.py +253 -0
- atlas_init/repos/cfn.py +6 -1
- atlas_init/repos/path.py +3 -3
- atlas_init/settings/config.py +30 -11
- atlas_init/settings/env_vars.py +29 -3
- atlas_init/settings/path.py +12 -1
- atlas_init/settings/rich_utils.py +39 -2
- atlas_init/terraform.yaml +77 -1
- atlas_init/tf/.terraform.lock.hcl +125 -0
- atlas_init/tf/always.tf +11 -2
- atlas_init/tf/main.tf +3 -0
- atlas_init/tf/modules/aws_s3/provider.tf +1 -1
- atlas_init/tf/modules/aws_vars/aws_vars.tf +2 -0
- atlas_init/tf/modules/aws_vpc/provider.tf +4 -1
- atlas_init/tf/modules/cfn/cfn.tf +47 -33
- atlas_init/tf/modules/cfn/kms.tf +54 -0
- atlas_init/tf/modules/cfn/resource_actions.yaml +1 -0
- atlas_init/tf/modules/cfn/variables.tf +31 -0
- atlas_init/tf/modules/cloud_provider/cloud_provider.tf +1 -0
- atlas_init/tf/modules/cloud_provider/provider.tf +1 -1
- atlas_init/tf/modules/cluster/cluster.tf +34 -24
- atlas_init/tf/modules/cluster/provider.tf +1 -1
- atlas_init/tf/modules/federated_vars/federated_vars.tf +3 -0
- atlas_init/tf/modules/federated_vars/provider.tf +1 -1
- atlas_init/tf/modules/project_extra/project_extra.tf +15 -1
- atlas_init/tf/modules/stream_instance/stream_instance.tf +1 -1
- atlas_init/tf/modules/vpc_peering/vpc_peering.tf +1 -1
- atlas_init/tf/modules/vpc_privatelink/versions.tf +1 -1
- atlas_init/tf/outputs.tf +11 -3
- atlas_init/tf/providers.tf +2 -1
- atlas_init/tf/variables.tf +17 -0
- atlas_init/typer_app.py +76 -0
- {atlas_init-0.1.0.dist-info → atlas_init-0.1.4.dist-info}/METADATA +58 -21
- atlas_init-0.1.4.dist-info/RECORD +91 -0
- {atlas_init-0.1.0.dist-info → atlas_init-0.1.4.dist-info}/WHEEL +1 -1
- atlas_init-0.1.0.dist-info/RECORD +0 -61
- /atlas_init/tf/modules/aws_vpc/{aws-vpc.tf → aws_vpc.tf} +0 -0
- {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
|
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(
|
33
|
-
|
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
|
}
|
atlas_init/settings/config.py
CHANGED
@@ -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
|
-
|
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
|
-
|
149
|
-
|
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
|
atlas_init/settings/env_vars.py
CHANGED
@@ -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
|
-
|
50
|
+
non_interactive: bool = False
|
51
51
|
|
52
52
|
@property
|
53
53
|
def is_interactive(self) -> bool:
|
54
|
-
return not self.
|
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 {
|
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
|
|