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 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} can not be destroyed for any reason')
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 deletion in changes['unauthorized']:
73
- printer.verbose(f" - {deletion}")
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
- for resource in detected_changes[key]:
135
+
136
+ for index, resource in enumerate(detected_changes[key]):
118
137
  resource_address = resource["address"]
119
-
120
- has_match, resource_config = get_matching_dd_config(resource_address, dd_config)
121
- if has_match:
122
- # so what drift classification do we have?
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
- changes['drifts']['unknown'] += 1
127
- if resource_address not in changes['unknowns']:
128
- changes['unknowns'].append(resource_address)
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
- changes['drifts']['total'] += 1
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
- return changes, (not changes['unauthorized'])
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"\[(.+?)\]", "[[]\g<1>[]]", pattern)
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"\[(.+?)\]", "[[]\g<1>[]]", pattern)
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 KeyError:
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 + ['state', 'render-json']))
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=False,
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=False,
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=None,
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=False,
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/--include-external-dependencies', '-x/-i',
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=None,
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
- include_dir, exclude_dir, planfile_dir, terragrunt_args):
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('--manifest-file', '-m',
692
- help='Manifest file describing the deployment options',
693
- required=True, default="manifest.yaml", show_default=True,
694
- type=click.Path(),
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(manifest_file, verbose, working_dir, out):
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
- manifest_file=manifest_file,
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
- include_modules = substack['include_modules'] if len(substack.get('include_modules', {})) > 0 else []
187
- printer.verbose(f'Include modules: {include_modules}')
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}}"