tgwrap 0.8.12__py3-none-any.whl → 0.11.2__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.
- tgwrap/analyze.py +62 -18
- tgwrap/cli.py +117 -25
- tgwrap/deploy.py +10 -3
- tgwrap/inspector-resources-template.yml +63 -0
- tgwrap/inspector.py +438 -0
- tgwrap/main.py +583 -126
- tgwrap/printer.py +3 -0
- {tgwrap-0.8.12.dist-info → tgwrap-0.11.2.dist-info}/METADATA +163 -6
- tgwrap-0.11.2.dist-info/RECORD +13 -0
- {tgwrap-0.8.12.dist-info → tgwrap-0.11.2.dist-info}/WHEEL +1 -1
- tgwrap-0.8.12.dist-info/RECORD +0 -11
- {tgwrap-0.8.12.dist-info → tgwrap-0.11.2.dist-info}/LICENSE +0 -0
- {tgwrap-0.8.12.dist-info → tgwrap-0.11.2.dist-info}/entry_points.txt +0 -0
tgwrap/analyze.py
CHANGED
@@ -9,7 +9,7 @@ import re
|
|
9
9
|
|
10
10
|
from .printer import Printer
|
11
11
|
|
12
|
-
def run_analyze(config, data, verbose=False):
|
12
|
+
def run_analyze(config, data, ignore_attributes, verbose=False):
|
13
13
|
""" Run the terrasafe validation and return the unauthorized deletions """
|
14
14
|
|
15
15
|
printer = Printer(verbose)
|
@@ -44,11 +44,29 @@ def run_analyze(config, data, verbose=False):
|
|
44
44
|
for criticallity, ts_level in ts_default_levels.items():
|
45
45
|
ts_config[ts_level] = [f"*{key}*" for key, value in config[criticallity].items() if value.get('terrasafe_level', ts_level) == ts_level]
|
46
46
|
|
47
|
+
ignorable_updates = []
|
48
|
+
for resource in detected_changes['updates']:
|
49
|
+
before = resource['change']['before']
|
50
|
+
after = resource['change']['after']
|
51
|
+
|
52
|
+
for i in ignore_attributes:
|
53
|
+
try:
|
54
|
+
before.pop(i)
|
55
|
+
except KeyError:
|
56
|
+
pass
|
57
|
+
try:
|
58
|
+
after.pop(i)
|
59
|
+
except KeyError:
|
60
|
+
pass
|
61
|
+
|
62
|
+
if before == after:
|
63
|
+
ignorable_updates.append(resource['address'])
|
64
|
+
|
47
65
|
for resource in detected_changes['deletions']:
|
48
66
|
resource_address = resource["address"]
|
49
67
|
# Deny has precendence over Allow!
|
50
68
|
if is_resource_match_any(resource_address, ts_config["unauthorized_deletion"]):
|
51
|
-
printer.verbose(f'Resource {resource_address}
|
69
|
+
printer.verbose(f'Resource {resource_address} cannot be destroyed for any reason')
|
52
70
|
elif is_resource_match_any(resource_address, ts_config["ignore_deletion"]):
|
53
71
|
continue
|
54
72
|
elif is_resource_recreate(resource) and is_resource_match_any(
|
@@ -69,8 +87,8 @@ def run_analyze(config, data, verbose=False):
|
|
69
87
|
|
70
88
|
if changes['unauthorized']:
|
71
89
|
printer.verbose("Unauthorized deletion detected for those resources:")
|
72
|
-
for
|
73
|
-
printer.verbose(f" - {
|
90
|
+
for resource in changes['unauthorized']:
|
91
|
+
printer.verbose(f" - {resource}")
|
74
92
|
printer.verbose("If you really want to delete those resources: comment it or export this environment variable:")
|
75
93
|
printer.verbose(f"export TERRASAFE_ALLOW_DELETION=\"{';'.join(changes['unauthorized'])}\"")
|
76
94
|
|
@@ -114,20 +132,26 @@ def run_analyze(config, data, verbose=False):
|
|
114
132
|
|
115
133
|
# now we have the proper lists, calculate the drifts
|
116
134
|
for key, value in {'deletions': 'delete', 'creations': 'create', 'updates': 'update'}.items():
|
117
|
-
|
135
|
+
|
136
|
+
for index, resource in enumerate(detected_changes[key]):
|
118
137
|
resource_address = resource["address"]
|
119
|
-
|
120
|
-
|
121
|
-
if
|
122
|
-
|
123
|
-
dd_class = resource_config[value]
|
124
|
-
changes['drifts'][dd_class] += 1
|
138
|
+
|
139
|
+
# updates might need to be ignored
|
140
|
+
if key == 'updates' and resource_address in ignorable_updates:
|
141
|
+
detected_changes[key].pop(index)
|
125
142
|
else:
|
126
|
-
|
127
|
-
|
128
|
-
|
143
|
+
has_match, resource_config = get_matching_dd_config(resource_address, dd_config)
|
144
|
+
|
145
|
+
if has_match:
|
146
|
+
# so what drift classification do we have?
|
147
|
+
dd_class = resource_config[value]
|
148
|
+
changes['drifts'][dd_class] += 1
|
149
|
+
else:
|
150
|
+
changes['drifts']['unknown'] += 1
|
151
|
+
if resource_address not in changes['unknowns']:
|
152
|
+
changes['unknowns'].append(resource_address)
|
129
153
|
|
130
|
-
|
154
|
+
changes['drifts']['total'] += 1
|
131
155
|
|
132
156
|
# remove ballast from the following lists
|
133
157
|
changes['all'] = [ # ignore read and no-ops
|
@@ -140,9 +164,17 @@ def run_analyze(config, data, verbose=False):
|
|
140
164
|
]
|
141
165
|
changes['creations'] = [resource["address"] for resource in detected_changes['creations']]
|
142
166
|
changes['updates'] = [resource["address"] for resource in detected_changes['updates']]
|
167
|
+
changes['ignorable_updates'] = ignorable_updates
|
143
168
|
|
144
|
-
|
169
|
+
# see if there are output changes
|
170
|
+
output_changes = get_output_changes(data)
|
171
|
+
changes['outputs'] = []
|
172
|
+
relevant_changes = set(['create', 'update', 'delete'])
|
173
|
+
for k,v in output_changes.items():
|
174
|
+
if relevant_changes.intersection(v['actions']):
|
175
|
+
changes['outputs'].append(k)
|
145
176
|
|
177
|
+
return changes, (not changes['unauthorized'])
|
146
178
|
|
147
179
|
def parse_ignored_from_env_var():
|
148
180
|
ignored = os.environ.get("TERRASAFE_ALLOW_DELETION")
|
@@ -170,6 +202,18 @@ def get_resource_actions(data):
|
|
170
202
|
|
171
203
|
return changes
|
172
204
|
|
205
|
+
def get_output_changes(data):
|
206
|
+
# check format version
|
207
|
+
if data["format_version"].split(".")[0] != "0" and data["format_version"].split(".")[0] != "1":
|
208
|
+
raise Exception("Only format major version 0 or 1 is supported")
|
209
|
+
|
210
|
+
if "output_changes" in data:
|
211
|
+
output_changes = data["output_changes"]
|
212
|
+
else:
|
213
|
+
output_changes = {}
|
214
|
+
|
215
|
+
return output_changes
|
216
|
+
|
173
217
|
|
174
218
|
def has_delete_action(resource):
|
175
219
|
return "delete" in resource["change"]["actions"]
|
@@ -185,7 +229,7 @@ def has_update_action(resource):
|
|
185
229
|
|
186
230
|
def is_resource_match_any(resource_address, pattern_list):
|
187
231
|
for pattern in pattern_list:
|
188
|
-
pattern = re.sub(r"\[(.+?)\]", "[[]
|
232
|
+
pattern = re.sub(r"\[(.+?)\]", "[[]\\g<1>[]]", pattern)
|
189
233
|
if fnmatch.fnmatch(resource_address, pattern):
|
190
234
|
return True
|
191
235
|
return False
|
@@ -193,7 +237,7 @@ def is_resource_match_any(resource_address, pattern_list):
|
|
193
237
|
|
194
238
|
def get_matching_dd_config(resource_address, dd_config):
|
195
239
|
for pattern, config in dd_config.items():
|
196
|
-
pattern = re.sub(r"\[(.+?)\]", "[[]
|
240
|
+
pattern = re.sub(r"\[(.+?)\]", "[[]\\g<1>[]]", pattern)
|
197
241
|
if fnmatch.fnmatch(resource_address, pattern):
|
198
242
|
return True, config
|
199
243
|
return False, None
|
tgwrap/cli.py
CHANGED
@@ -42,7 +42,7 @@ def check_latest_version(verbose=False):
|
|
42
42
|
# this happens when your local version is ahead of the pypi version,
|
43
43
|
# which happens only in development
|
44
44
|
pass
|
45
|
-
except
|
45
|
+
except :
|
46
46
|
echo('Could not determine package version, continue nevertheless.')
|
47
47
|
pass
|
48
48
|
|
@@ -50,7 +50,7 @@ CLICK_CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
50
50
|
|
51
51
|
TG_COMMANDS=[
|
52
52
|
'init', 'validate', 'validate-inputs', 'plan', 'apply',
|
53
|
-
'destroy', 'info', 'output', 'show',
|
53
|
+
'destroy', 'info', 'output', 'show', 'state', 'force-unlock'
|
54
54
|
]
|
55
55
|
STAGES=['global', 'sbx', 'dev', 'qas', 'run', 'tst', 'acc', 'prd']
|
56
56
|
class DefaultGroup(click.Group):
|
@@ -107,7 +107,7 @@ def main():
|
|
107
107
|
ignore_unknown_options=True,
|
108
108
|
),
|
109
109
|
)
|
110
|
-
@click.argument('command', type=click.Choice(TG_COMMANDS + ['
|
110
|
+
@click.argument('command', type=click.Choice(TG_COMMANDS + ['render-json']))
|
111
111
|
@click.option('--verbose', '-v', is_flag=True, default=False,
|
112
112
|
help='Verbose printing',
|
113
113
|
show_default=True
|
@@ -120,7 +120,7 @@ def main():
|
|
120
120
|
help='dry-run mode, no real actions are executed (only in combination with step-by-step mode)',
|
121
121
|
show_default=True
|
122
122
|
)
|
123
|
-
@click.option('--no-lock', '-n', is_flag=True, default=
|
123
|
+
@click.option('--no-lock', '-n', is_flag=True, default=True,
|
124
124
|
help='Do not apply a lock while executing the command (or set the TGWRAP_NO_LOCK environment variable, only applicable with plan)',
|
125
125
|
envvar='TGWRAP_NO_LOCK', show_default=True,
|
126
126
|
)
|
@@ -189,7 +189,7 @@ def run(command, verbose, debug, dry_run, no_lock, update, upgrade,
|
|
189
189
|
help='dry-run mode, no real actions are executed (only in combination with step-by-step mode)',
|
190
190
|
show_default=True
|
191
191
|
)
|
192
|
-
@click.option('--no-lock', '-n', is_flag=True, default=
|
192
|
+
@click.option('--no-lock', '-n', is_flag=True, default=True,
|
193
193
|
help='Do not apply a lock while executing the command (or set the TGWRAP_NO_LOCK environment variable, only applicable with plan)',
|
194
194
|
envvar='TGWRAP_NO_LOCK', show_default=True,
|
195
195
|
)
|
@@ -210,6 +210,10 @@ def run(command, verbose, debug, dry_run, no_lock, update, upgrade,
|
|
210
210
|
help='Run the command step by step and stop when an error occurs (where applicable)',
|
211
211
|
show_default=True
|
212
212
|
)
|
213
|
+
@click.option('--continue-on-error', '-C', is_flag=True, default=False,
|
214
|
+
help='When running in step by step, continue when an error occurs',
|
215
|
+
show_default=True
|
216
|
+
)
|
213
217
|
@click.option('--planfile', '-p', is_flag=True, default=False,
|
214
218
|
help='Use the generated planfile when applying the changes',
|
215
219
|
show_default=True
|
@@ -233,18 +237,18 @@ def run(command, verbose, debug, dry_run, no_lock, update, upgrade,
|
|
233
237
|
)
|
234
238
|
@click.option('--include-dir', '-I',
|
235
239
|
multiple=True, default=[],
|
236
|
-
help='A glob of a directory that needs to be included, this option can be used multiple times. For example: -I "integrations/\*/\*"',
|
240
|
+
help=r'A glob of a directory that needs to be included, this option can be used multiple times. For example: -I "integrations/\*/\*"',
|
237
241
|
show_default=True
|
238
242
|
)
|
239
243
|
@click.option('--exclude-dir', '-E',
|
240
244
|
multiple=True, default=[],
|
241
|
-
help='A glob of a directory that needs to be excluded, this option can be used multiple times. For example: -E "integrations/\*/\*"',
|
245
|
+
help=r'A glob of a directory that needs to be excluded, this option can be used multiple times. For example: -E "integrations/\*/\*"',
|
242
246
|
show_default=True,
|
243
247
|
)
|
244
248
|
@click.argument('terragrunt-args', nargs=-1, type=click.UNPROCESSED)
|
245
249
|
@click.version_option(version=__version__)
|
246
250
|
def run_all(command, verbose, debug, dry_run, no_lock, update, upgrade, exclude_external_dependencies,
|
247
|
-
step_by_step, planfile, auto_approve, clean, working_dir, start_at_step,
|
251
|
+
step_by_step, continue_on_error, planfile, auto_approve, clean, working_dir, start_at_step,
|
248
252
|
limit_parallelism, include_dir, exclude_dir, terragrunt_args):
|
249
253
|
""" Executes a terragrunt command across multiple projects """
|
250
254
|
|
@@ -260,6 +264,7 @@ def run_all(command, verbose, debug, dry_run, no_lock, update, upgrade, exclude_
|
|
260
264
|
upgrade=upgrade,
|
261
265
|
exclude_external_dependencies=exclude_external_dependencies,
|
262
266
|
step_by_step=step_by_step,
|
267
|
+
continue_on_error=continue_on_error,
|
263
268
|
planfile=planfile,
|
264
269
|
auto_approve=auto_approve,
|
265
270
|
clean=clean,
|
@@ -287,9 +292,10 @@ def run_all(command, verbose, debug, dry_run, no_lock, update, upgrade, exclude_
|
|
287
292
|
@click.option('--json', '-j', is_flag=True, default=False,
|
288
293
|
help='Show output in json format',
|
289
294
|
)
|
290
|
-
@click.option('--planfile-dir', '-P', default=
|
295
|
+
@click.option('--planfile-dir', '-P', default='.terragrunt-cache/current',
|
291
296
|
help='Relative path to directory with plan file (or set TGWRAP_PLANFILE_DIR environment variable), see README for more details',
|
292
297
|
envvar='TGWRAP_PLANFILE_DIR', type=click.Path(),
|
298
|
+
show_default=True,
|
293
299
|
)
|
294
300
|
@click.argument('terragrunt-args', nargs=-1, type=click.UNPROCESSED)
|
295
301
|
@click.version_option(version=__version__)
|
@@ -329,7 +335,7 @@ def show(verbose, json, working_dir, planfile_dir, terragrunt_args):
|
|
329
335
|
@click.option('--working-dir', '-w', default=None,
|
330
336
|
help='Working directory, when omitted the current directory is used',
|
331
337
|
)
|
332
|
-
@click.option('--no-lock', '-n', is_flag=True, default=
|
338
|
+
@click.option('--no-lock', '-n', is_flag=True, default=True,
|
333
339
|
help='Do not apply a lock while executing the command (or set the TGWRAP_NO_LOCK environment variable, only applicable with plan)',
|
334
340
|
envvar='TGWRAP_NO_LOCK', show_default=True,
|
335
341
|
)
|
@@ -360,7 +366,7 @@ def run_import(address, id, verbose, dry_run, working_dir, no_lock, terragrunt_a
|
|
360
366
|
help='Verbose printing',
|
361
367
|
show_default=True
|
362
368
|
)
|
363
|
-
@click.option('--exclude-external-dependencies
|
369
|
+
@click.option('--exclude-external-dependencies', '-x',
|
364
370
|
is_flag=True, default=True,
|
365
371
|
help='Whether or not external dependencies must be ignored',
|
366
372
|
show_default=True
|
@@ -382,25 +388,37 @@ def run_import(address, id, verbose, dry_run, working_dir, no_lock, terragrunt_a
|
|
382
388
|
@click.option('--parallel-execution', '-p', is_flag=True, default=False,
|
383
389
|
help='Whether or not to use parallel execution',
|
384
390
|
)
|
391
|
+
@click.option('--ignore-attributes', '-i',
|
392
|
+
multiple=True, default=[],
|
393
|
+
help=r'A glob of attributes for which updates can be ignored, this option can be used multiple times (or set TGWRAP_ANALYZE_IGNORE environment variable)',
|
394
|
+
envvar='TGWRAP_ANALYZE_IGNORE',
|
395
|
+
show_default=True
|
396
|
+
)
|
385
397
|
@click.option('--include-dir', '-I',
|
386
398
|
multiple=True, default=[],
|
387
|
-
help='A glob of a directory that needs to be included, this option can be used multiple times. For example: -I "integrations/\*/\*"',
|
399
|
+
help=r'A glob of a directory that needs to be included, this option can be used multiple times. For example: -I "integrations/\*/\*"',
|
388
400
|
show_default=True
|
389
401
|
)
|
390
402
|
@click.option('--exclude-dir', '-E',
|
391
403
|
multiple=True, default=[],
|
392
|
-
help='A glob of a directory that needs to be excluded, this option can be used multiple times. For example: -E "integrations/\*/\*"',
|
404
|
+
help=r'A glob of a directory that needs to be excluded, this option can be used multiple times. For example: -E "integrations/\*/\*"',
|
393
405
|
show_default=True,
|
394
406
|
)
|
395
|
-
@click.option('--planfile-dir', '-P', default=
|
407
|
+
@click.option('--planfile-dir', '-P', default='.terragrunt-cache/current',
|
396
408
|
help='Relative path to directory with plan file (or set TGWRAP_PLANFILE_DIR environment variable), see README for more details',
|
397
409
|
envvar='TGWRAP_PLANFILE_DIR', type=click.Path(),
|
410
|
+
show_default=True,
|
411
|
+
)
|
412
|
+
@click.option('--data-collection-endpoint', '-D', default=None,
|
413
|
+
help='Optional URI of an (Azure) data collection endpoint, to which the analyse results will be sent',
|
414
|
+
envvar='TGWRAP_ANALYZE_DATA_COLLECTION_ENDPOINT',
|
415
|
+
show_default=True,
|
398
416
|
)
|
399
417
|
@click.argument('terragrunt-args', nargs=-1, type=click.UNPROCESSED)
|
400
418
|
@click.version_option(version=__version__)
|
401
419
|
def run_analyze(verbose, exclude_external_dependencies, working_dir, start_at_step,
|
402
|
-
out, analyze_config, parallel_execution,
|
403
|
-
|
420
|
+
out, analyze_config, parallel_execution, ignore_attributes, include_dir, exclude_dir,
|
421
|
+
planfile_dir, data_collection_endpoint, terragrunt_args):
|
404
422
|
""" Analyzes the plan files """
|
405
423
|
|
406
424
|
check_latest_version(verbose)
|
@@ -413,9 +431,11 @@ def run_analyze(verbose, exclude_external_dependencies, working_dir, start_at_st
|
|
413
431
|
out=out,
|
414
432
|
analyze_config=analyze_config,
|
415
433
|
parallel_execution=parallel_execution,
|
434
|
+
ignore_attributes=ignore_attributes,
|
416
435
|
include_dirs=include_dir,
|
417
436
|
exclude_dirs=exclude_dir,
|
418
437
|
planfile_dir=planfile_dir,
|
438
|
+
data_collection_endpoint=data_collection_endpoint,
|
419
439
|
terragrunt_args=terragrunt_args,
|
420
440
|
)
|
421
441
|
|
@@ -688,10 +708,15 @@ def deploy(
|
|
688
708
|
ignore_unknown_options=True,
|
689
709
|
),
|
690
710
|
)
|
691
|
-
@click.option('--
|
692
|
-
help='
|
693
|
-
required=True,
|
694
|
-
|
711
|
+
@click.option('--platform-repo-url', '-p',
|
712
|
+
help='URL of the platform git repository',
|
713
|
+
required=True,
|
714
|
+
envvar='TGWRAP_PLATFORM_REPO_URL'
|
715
|
+
)
|
716
|
+
@click.option('--levels-deep', '-l',
|
717
|
+
help='For how many (directory) levels deep must be searched for deployments',
|
718
|
+
required=True, default=5, show_default=True,
|
719
|
+
type=int,
|
695
720
|
)
|
696
721
|
@click.option('--verbose', '-v',
|
697
722
|
help='Verbose printing',
|
@@ -705,14 +730,15 @@ def deploy(
|
|
705
730
|
show_default=True
|
706
731
|
)
|
707
732
|
@click.version_option(version=__version__)
|
708
|
-
def check_deployments(
|
733
|
+
def check_deployments(platform_repo_url, levels_deep, verbose, working_dir, out):
|
709
734
|
""" Check the freshness of deployed configuration versions against the platform repository """
|
710
735
|
|
711
736
|
check_latest_version(verbose)
|
712
737
|
|
713
738
|
tgwrap = TgWrap(verbose=verbose)
|
714
739
|
tgwrap.check_deployments(
|
715
|
-
|
740
|
+
repo_url=platform_repo_url,
|
741
|
+
levels_deep=levels_deep,
|
716
742
|
working_dir=working_dir,
|
717
743
|
out=out,
|
718
744
|
)
|
@@ -737,14 +763,19 @@ def check_deployments(manifest_file, verbose, working_dir, out):
|
|
737
763
|
help='Whether or not external dependencies must be ignored',
|
738
764
|
show_default=True
|
739
765
|
)
|
766
|
+
@click.option('--analyze', '-a',
|
767
|
+
is_flag=True, default=False,
|
768
|
+
help='Show analysis of the graph',
|
769
|
+
show_default=True
|
770
|
+
)
|
740
771
|
@click.option('--include-dir', '-I',
|
741
772
|
multiple=True, default=[],
|
742
|
-
help='A glob of a directory that needs to be included, this option can be used multiple times. For example: -I "integrations/\*/\*"',
|
773
|
+
help=r'A glob of a directory that needs to be included, this option can be used multiple times. For example: -I "integrations/\*/\*"',
|
743
774
|
show_default=True
|
744
775
|
)
|
745
776
|
@click.option('--exclude-dir', '-E',
|
746
777
|
multiple=True, default=[],
|
747
|
-
help='A glob of a directory that needs to be excluded, this option can be used multiple times. For example: -E "integrations/\*/\*"',
|
778
|
+
help=r'A glob of a directory that needs to be excluded, this option can be used multiple times. For example: -E "integrations/\*/\*"',
|
748
779
|
show_default=True,
|
749
780
|
)
|
750
781
|
@click.option('--working-dir', '-w', default=None,
|
@@ -752,7 +783,7 @@ def check_deployments(manifest_file, verbose, working_dir, out):
|
|
752
783
|
)
|
753
784
|
@click.argument('terragrunt-args', nargs=-1, type=click.UNPROCESSED)
|
754
785
|
@click.version_option(version=__version__)
|
755
|
-
def show_graph(verbose, backwards, exclude_external_dependencies, working_dir, include_dir, exclude_dir, terragrunt_args):
|
786
|
+
def show_graph(verbose, backwards, exclude_external_dependencies, analyze, working_dir, include_dir, exclude_dir, terragrunt_args):
|
756
787
|
""" Shows the dependencies of a project """
|
757
788
|
|
758
789
|
check_latest_version(verbose)
|
@@ -761,6 +792,7 @@ def show_graph(verbose, backwards, exclude_external_dependencies, working_dir, i
|
|
761
792
|
tgwrap.show_graph(
|
762
793
|
backwards=backwards,
|
763
794
|
exclude_external_dependencies=exclude_external_dependencies,
|
795
|
+
analyze=analyze,
|
764
796
|
working_dir=working_dir,
|
765
797
|
include_dirs=include_dir,
|
766
798
|
exclude_dirs=exclude_dir,
|
@@ -826,6 +858,66 @@ def change_log(changelog_file, verbose, working_dir, include_nbr_of_releases):
|
|
826
858
|
include_nbr_of_releases=include_nbr_of_releases,
|
827
859
|
)
|
828
860
|
|
861
|
+
@main.command(
|
862
|
+
name="inspect",
|
863
|
+
context_settings=dict(
|
864
|
+
ignore_unknown_options=True,
|
865
|
+
),
|
866
|
+
)
|
867
|
+
@click.option('--domain', '-d',
|
868
|
+
help='Domain name used in naming the objects',
|
869
|
+
required=True,
|
870
|
+
)
|
871
|
+
@click.option('--substack', '-S',
|
872
|
+
help='Identifier that is needed to select the objects',
|
873
|
+
default=None,
|
874
|
+
)
|
875
|
+
@click.option('--stage', '-s',
|
876
|
+
help='Stage (environment) to verify',
|
877
|
+
required=True,
|
878
|
+
)
|
879
|
+
@click.option('--azure-subscription-id', '-a',
|
880
|
+
help='Azure subscription id',
|
881
|
+
required=True,
|
882
|
+
)
|
883
|
+
@click.option('--config-file', '-c',
|
884
|
+
help='Config file specifying the verifications',
|
885
|
+
required=True,
|
886
|
+
type=click.Path(),
|
887
|
+
)
|
888
|
+
@click.option('--out', '-o', is_flag=True, default=False,
|
889
|
+
help='Show output as json',
|
890
|
+
show_default=True
|
891
|
+
)
|
892
|
+
@click.option('--data-collection-endpoint', '-D', default=None,
|
893
|
+
help='Optional URI of an (Azure) data collection endpoint, to which the inspection results will be sent',
|
894
|
+
envvar='TGWRAP_INSPECT_DATA_COLLECTION_ENDPOINT',
|
895
|
+
show_default=True,
|
896
|
+
)
|
897
|
+
@click.option('--verbose', '-v', is_flag=True, default=False,
|
898
|
+
help='Verbose printing',
|
899
|
+
show_default=True
|
900
|
+
)
|
901
|
+
@click.version_option(version=__version__)
|
902
|
+
def inspect(domain, substack, stage, azure_subscription_id, config_file, out,
|
903
|
+
data_collection_endpoint, verbose):
|
904
|
+
""" Inspect the status of an (Azure) environment """
|
905
|
+
|
906
|
+
check_latest_version(verbose)
|
907
|
+
|
908
|
+
tgwrap = TgWrap(verbose=verbose)
|
909
|
+
exit = tgwrap.inspect(
|
910
|
+
domain=domain,
|
911
|
+
substack=substack,
|
912
|
+
stage=stage,
|
913
|
+
azure_subscription_id=azure_subscription_id,
|
914
|
+
out=out,
|
915
|
+
data_collection_endpoint=data_collection_endpoint,
|
916
|
+
config_file=config_file,
|
917
|
+
)
|
918
|
+
|
919
|
+
sys.exit(exit)
|
920
|
+
|
829
921
|
# this is needed for the vscode debugger to work
|
830
922
|
if __name__ == '__main__':
|
831
923
|
main()
|
tgwrap/deploy.py
CHANGED
@@ -179,12 +179,16 @@ def prepare_deploy_config(step, config, source_dir, source_config_dir, target_di
|
|
179
179
|
source_path = os.path.join(
|
180
180
|
source_dir, source_stage, substack['source'], ''
|
181
181
|
)
|
182
|
+
source_modules = {
|
183
|
+
entry:{} for entry in os.listdir(source_path) if os.path.isdir(os.path.join(source_path, entry))
|
184
|
+
}
|
182
185
|
target_path = os.path.join(
|
183
186
|
target_dir, substack['target'], ''
|
184
187
|
)
|
185
188
|
|
186
|
-
|
187
|
-
|
189
|
+
printer.verbose(f'Include substack modules: {include_modules}')
|
190
|
+
|
191
|
+
include_modules = substack.get('include_modules', [])
|
188
192
|
|
189
193
|
if include_modules:
|
190
194
|
# get all directories in the substack and create an exlude_modules list from that
|
@@ -192,7 +196,10 @@ def prepare_deploy_config(step, config, source_dir, source_config_dir, target_di
|
|
192
196
|
exclude_modules = list(set(source_directories) - set(include_modules))
|
193
197
|
else:
|
194
198
|
exclude_modules = substack.get('exclude_modules', [])
|
195
|
-
|
199
|
+
|
200
|
+
printer.verbose(f'Include modules: {include_modules}')
|
201
|
+
printer.verbose(f'Exclude modules: {include_modules}')
|
202
|
+
|
196
203
|
if os.path.exists(source_path):
|
197
204
|
deploy_actions[f'substack -> {substack["target"]}'] = {
|
198
205
|
"source": source_path,
|
@@ -0,0 +1,63 @@
|
|
1
|
+
---
|
2
|
+
entra_id_group:
|
3
|
+
# note that there must be single quotes around the {name}, double quotes are not working!
|
4
|
+
url: "https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '{name}'"
|
5
|
+
properties: {}
|
6
|
+
subscription:
|
7
|
+
url: https://management.azure.com/subscriptions/{subscription_id}?api-version=2024-03-01
|
8
|
+
properties:
|
9
|
+
state: Enabled
|
10
|
+
resource_group:
|
11
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{name}?api-version=2024-03-01
|
12
|
+
properties:
|
13
|
+
properties.provisioningState: Succeeded
|
14
|
+
location: "{{location_code}}"
|
15
|
+
key_vault:
|
16
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.KeyVault/vaults/{name}?api-version=2019-09-01
|
17
|
+
properties:
|
18
|
+
properties.provisioningState: Succeeded
|
19
|
+
location: "{{location_code}}"
|
20
|
+
application_insights:
|
21
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.Insights/components/{name}?api-version=2014-04-01
|
22
|
+
properties:
|
23
|
+
properties.provisioningState: Succeeded
|
24
|
+
location: "{{location_code}}"
|
25
|
+
log_analytics_workspace:
|
26
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.OperationalInsights/workspaces/{name}?api-version=2021-06-01
|
27
|
+
properties:
|
28
|
+
properties.provisioningState: Succeeded
|
29
|
+
location: "{{location_code}}"
|
30
|
+
storage_account:
|
31
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.Storage/storageAccounts/{name}?api-version=2021-08-01
|
32
|
+
properties:
|
33
|
+
properties.provisioningState: Succeeded
|
34
|
+
kind: StorageV2
|
35
|
+
location: "{{location_code}}"
|
36
|
+
databricks_access_connector:
|
37
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.Databricks/accessConnectors/{name}?api-version=2024-05-01
|
38
|
+
properties:
|
39
|
+
properties.provisioningState: Succeeded
|
40
|
+
location: "{{location_full}}"
|
41
|
+
databricks_workspace:
|
42
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.Databricks/workspaces/{name}?api-version=2024-05-01
|
43
|
+
properties:
|
44
|
+
properties.provisioningState: Succeeded
|
45
|
+
location: "{{location_code}}"
|
46
|
+
machine_learning_workspace:
|
47
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.MachineLearningServices/workspaces/{name}?api-version=2023-04-01
|
48
|
+
properties:
|
49
|
+
properties.provisioningState: Succeeded
|
50
|
+
location: "{{location_code}}"
|
51
|
+
data_factory:
|
52
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.DataFactory/factories/{name}?api-version=2018-06-01
|
53
|
+
properties:
|
54
|
+
properties.provisioningState: Succeeded
|
55
|
+
properties.repoConfiguration.repositoryName: datafactory
|
56
|
+
properties.repoConfiguration.type: FactoryVSTSConfiguration
|
57
|
+
location: "{{location_code}}"
|
58
|
+
function_app:
|
59
|
+
url: https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.Web/sites/{name}?api-version=2021-01-15
|
60
|
+
properties:
|
61
|
+
properties.state: Running
|
62
|
+
properties.enabled: true
|
63
|
+
location: "{{location_full}}"
|