tgwrap 0.8.11__py3-none-any.whl → 0.8.13__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/cli.py CHANGED
@@ -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',
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
@@ -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
@@ -244,7 +248,7 @@ def run(command, verbose, debug, dry_run, no_lock, update, upgrade,
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,
@@ -392,9 +397,10 @@ def run_import(address, id, verbose, dry_run, working_dir, no_lock, terragrunt_a
392
397
  help='A glob of a directory that needs to be excluded, this option can be used multiple times. For example: -E "integrations/\*/\*"',
393
398
  show_default=True,
394
399
  )
395
- @click.option('--planfile-dir', '-P', default=None,
400
+ @click.option('--planfile-dir', '-P', default='.terragrunt-cache/current',
396
401
  help='Relative path to directory with plan file (or set TGWRAP_PLANFILE_DIR environment variable), see README for more details',
397
402
  envvar='TGWRAP_PLANFILE_DIR', type=click.Path(),
403
+ show_default=True,
398
404
  )
399
405
  @click.argument('terragrunt-args', nargs=-1, type=click.UNPROCESSED)
400
406
  @click.version_option(version=__version__)
tgwrap/deploy.py CHANGED
@@ -171,66 +171,69 @@ def prepare_deploy_config(step, config, source_dir, source_config_dir, target_di
171
171
  printer.warning(f'Source path of config file does not exist: {source_path}')
172
172
 
173
173
  for ss, substack in substack_configs:
174
- printer.verbose(f'Found substack : {ss}')
175
-
176
- source_path = os.path.join(
177
- source_dir, source_stage, substack['source'], ''
178
- )
179
- target_path = os.path.join(
180
- target_dir, substack['target'], ''
181
- )
182
-
183
- include_modules = substack['include_modules'] if len(substack.get('include_modules', {})) > 0 else []
184
- printer.verbose(f'Include modules: {include_modules}')
185
-
186
- if include_modules:
187
- # get all directories in the substack and create an exlude_modules list from that
188
- source_directories = get_directories(source_path)
189
- exclude_modules = list(set(source_directories) - set(include_modules))
190
- else:
191
- exclude_modules = substack.get('exclude_modules', [])
192
-
193
- if os.path.exists(source_path):
194
- deploy_actions[f'substack -> {substack["target"]}'] = {
195
- "source": source_path,
196
- "target": target_path,
197
- "excludes": exclude_modules,
198
- }
174
+ if 'applies_to_stages' in substack and target_stage not in substack['applies_to_stages']:
175
+ printer.verbose(f'Target stage {target_stage} not applicable for substack {ss}.')
199
176
  else:
200
- printer.warning(f'Source path of substack does not exist: {source_path}')
201
-
202
- if len(substack.get('configs', [])) > 0:
203
- # run some checks and sets some variables
204
- if not source_config_dir:
205
- raise Exception("Config files must be deployed but 'config_path' variable is not set!")
206
- elif not config_dir:
207
- raise Exception("Config files must be deployed but 'config_dir' variable is not set!")
177
+ printer.verbose(f'Found substack : {ss}')
208
178
 
179
+ source_path = os.path.join(
180
+ source_dir, source_stage, substack['source'], ''
181
+ )
209
182
  target_path = os.path.join(
210
- target_dir, config_dir, module_name, target_stage, substack['target'], '',
183
+ target_dir, substack['target'], ''
211
184
  )
212
- # the target path might not exist
213
- try:
214
- os.makedirs(target_path)
215
- except FileExistsError:
216
- pass
217
185
 
218
- for cfg in substack.get('configs', []):
219
- printer.verbose(f'Found substack config file : {cfg}')
186
+ include_modules = substack['include_modules'] if len(substack.get('include_modules', {})) > 0 else []
187
+ printer.verbose(f'Include modules: {include_modules}')
220
188
 
221
- full_source_path = os.path.join(
222
- source_config_dir, source_stage, substack['source'], cfg
223
- )
189
+ if include_modules:
190
+ # get all directories in the substack and create an exlude_modules list from that
191
+ source_directories = get_directories(source_path)
192
+ exclude_modules = list(set(source_directories) - set(include_modules))
193
+ else:
194
+ exclude_modules = substack.get('exclude_modules', [])
224
195
 
225
- full_target_path = os.path.dirname(os.path.join(target_path, cfg))
226
- # print("source: ", full_source_path)
227
- # print("target: ", full_target_path)
228
196
  if os.path.exists(source_path):
229
- deploy_actions[f"substack {substack['target']} configs -> {os.path.join(substack['source'], cfg)}"] = {
230
- "source": full_source_path,
231
- "target": full_target_path,
197
+ deploy_actions[f'substack -> {substack["target"]}'] = {
198
+ "source": source_path,
199
+ "target": target_path,
200
+ "excludes": exclude_modules,
232
201
  }
233
202
  else:
234
- printer.warning(f'Source path of config file does not exist: {source_path}')
203
+ printer.warning(f'Source path of substack does not exist: {source_path}')
204
+
205
+ if len(substack.get('configs', [])) > 0:
206
+ # run some checks and sets some variables
207
+ if not source_config_dir:
208
+ raise Exception("Config files must be deployed but 'config_path' variable is not set!")
209
+ elif not config_dir:
210
+ raise Exception("Config files must be deployed but 'config_dir' variable is not set!")
211
+
212
+ target_path = os.path.join(
213
+ target_dir, config_dir, module_name, target_stage, substack['target'], '',
214
+ )
215
+ # the target path might not exist
216
+ try:
217
+ os.makedirs(target_path)
218
+ except FileExistsError:
219
+ pass
220
+
221
+ for cfg in substack.get('configs', []):
222
+ printer.verbose(f'Found substack config file : {cfg}')
223
+
224
+ full_source_path = os.path.join(
225
+ source_config_dir, source_stage, substack['source'], cfg
226
+ )
227
+
228
+ full_target_path = os.path.dirname(os.path.join(target_path, cfg))
229
+ # print("source: ", full_source_path)
230
+ # print("target: ", full_target_path)
231
+ if os.path.exists(source_path):
232
+ deploy_actions[f"substack {substack['target']} configs -> {os.path.join(substack['source'], cfg)}"] = {
233
+ "source": full_source_path,
234
+ "target": full_target_path,
235
+ }
236
+ else:
237
+ printer.warning(f'Source path of config file does not exist: {source_path}')
235
238
 
236
239
  return deploy_actions
tgwrap/main.py CHANGED
@@ -102,7 +102,7 @@ class TgWrap():
102
102
  'info': '{base_command} terragrunt-info --terragrunt-non-interactive {update} {upgrade} {common}',
103
103
  'plan': '{base_command} {command} --terragrunt-non-interactive -out={planfile_name} {lock_level} {update} {parallelism} {common}',
104
104
  'apply': '{base_command} {command} {non_interactive} {no_auto_approve} {update} {parallelism} {common} {planfile}',
105
- 'show': '{base_command} {command} --terragrunt-non-interactive {planfile_name} {common}',
105
+ 'show': '{base_command} {command} --terragrunt-non-interactive {common} {planfile_name}',
106
106
  'destroy': '{base_command} {command} {non_interactive} {no_auto_approve} {parallelism} {common} {planfile}',
107
107
  }
108
108
 
@@ -656,7 +656,7 @@ class TgWrap():
656
656
  self, command, exclude_external_dependencies, start_at_step, dry_run,
657
657
  parallel_execution=False, ask_for_confirmation=False, collect_output_file=None,
658
658
  backwards=False, working_dir=None, include_dirs=[], exclude_dirs=[],
659
- use_native_terraform=False, add_to_workdir=None,
659
+ use_native_terraform=False, add_to_workdir=None, continue_on_error=False,
660
660
  ):
661
661
  "Runs the desired command in the directories as defined in the directed graph"
662
662
 
@@ -753,7 +753,7 @@ class TgWrap():
753
753
  progress=progress,
754
754
  )
755
755
 
756
- if stop_processing:
756
+ if stop_processing and not continue_on_error:
757
757
  self.printer.warning(f"Processing needs to be stopped at step {step_nbr}.")
758
758
  self.printer.normal(
759
759
  f"After you've fixed the problem, you can continue where you left off by adding '--start-at-step {step_nbr}'."
@@ -884,14 +884,14 @@ class TgWrap():
884
884
  # tgwrap state mv 'azuread_group.this["viewers"]' 'azuread_group.this["readers"]'
885
885
  rc = subprocess.run(
886
886
  shlex.split(cmd, posix=False),
887
- cwd=cwd,
887
+ cwd=cwd if cwd else None,
888
888
  )
889
889
  self.printer.verbose(rc)
890
890
 
891
891
  sys.exit(rc.returncode)
892
892
 
893
893
  def run_all(self, command, debug, dry_run, no_lock, update, upgrade,
894
- exclude_external_dependencies, step_by_step, planfile, auto_approve, clean,
894
+ exclude_external_dependencies, step_by_step, continue_on_error, planfile, auto_approve, clean,
895
895
  working_dir, start_at_step, limit_parallelism, include_dirs, exclude_dirs, terragrunt_args):
896
896
  """ Executes a terragrunt command across multiple modules """
897
897
 
@@ -903,10 +903,6 @@ class TgWrap():
903
903
  modifying_command = (command.lower() in ['apply', 'destroy'])
904
904
  auto_approve = auto_approve if modifying_command else True
905
905
 
906
- # if the dir is not ending on '/*', add it
907
- include_dirs = [dir.rstrip(f'.{os.path.sep}*') + f'{os.path.sep}*' for dir in include_dirs]
908
- exclude_dirs = [dir.rstrip(f'.{os.path.sep}*') + f'{os.path.sep}*' for dir in exclude_dirs]
909
-
910
906
  cmd = self._construct_command(
911
907
  command=command,
912
908
  allow_no_run_all=False,
@@ -933,11 +929,16 @@ class TgWrap():
933
929
  f'This command will be executed for each individual module:\n$ {cmd}'
934
930
  )
935
931
 
932
+ # if the dir is not ending on '/*', add it
933
+ include_dirs = [dir.rstrip(f'.{os.path.sep}*') + f'{os.path.sep}*' for dir in include_dirs]
934
+ exclude_dirs = [dir.rstrip(f'.{os.path.sep}*') + f'{os.path.sep}*' for dir in exclude_dirs]
935
+
936
936
  self._run_di_graph(
937
937
  command=cmd,
938
938
  exclude_external_dependencies=exclude_external_dependencies,
939
939
  working_dir=working_dir,
940
940
  ask_for_confirmation=(not auto_approve),
941
+ continue_on_error=continue_on_error,
941
942
  dry_run=dry_run,
942
943
  start_at_step=start_at_step,
943
944
  backwards=True if command.lower() in ['destroy'] else False,
@@ -1314,6 +1315,10 @@ class TgWrap():
1314
1315
  # and make it unique
1315
1316
  substacks = set(substacks)
1316
1317
 
1318
+ # the manifest file supports both `sub_stacks` and `substack` config name. Annoying to be a bit autistic when it comes to naming :-/
1319
+ substack_configs = manifest.get('sub_stacks', {})
1320
+ substack_configs.update(manifest.get('substacks', {}))
1321
+
1317
1322
  for target_stage in target_stages:
1318
1323
  target_dir = os.path.join(working_dir, '', target_stage)
1319
1324
  self.printer.header(f'Deploy stage {target_stage} to {target_dir}...')
@@ -1337,7 +1342,7 @@ class TgWrap():
1337
1342
  target_dir=target_dir,
1338
1343
  target_stage=target_stage,
1339
1344
  substacks=substacks,
1340
- substack_configs=manifest.get('sub_stacks', {}).items(),
1345
+ substack_configs=substack_configs.items(),
1341
1346
  tg_file_name=self.TERRAGRUNT_FILE,
1342
1347
  verbose=self.printer.print_verbose,
1343
1348
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tgwrap
3
- Version: 0.8.11
3
+ Version: 0.8.13
4
4
  Summary: A (terragrunt) wrapper around a (terraform) wrapper around ....
5
5
  Home-page: https://gitlab.com/lunadata/tgwrap
6
6
  License: MIT
@@ -230,7 +230,7 @@ deploy: # which modules do you want to deploy
230
230
  # - source: networking
231
231
  # - target: networking-connected
232
232
 
233
- sub_stacks:
233
+ substacks:
234
234
  is01:
235
235
  source: shared-integration/intsvc01
236
236
  target: integration/is01
@@ -240,6 +240,8 @@ sub_stacks:
240
240
  - my-ss-config.hcl
241
241
  - ../my-ss-config-dir
242
242
  is02:
243
+ applies_to_stages: # optional list of stages to include the substack in
244
+ - dev
243
245
  source: shared-integration/intsvc01
244
246
  target: integration/is02
245
247
 
@@ -0,0 +1,11 @@
1
+ tgwrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ tgwrap/analyze.py,sha256=CsSaGv-be6ATy36z9X7x00gpKY59soJys2VbIzD-tmg,8726
3
+ tgwrap/cli.py,sha256=sWUHP4EYUeKpYP3KgU3t2Er4YLlnwfXNVQkgVwmGFPM,29342
4
+ tgwrap/deploy.py,sha256=bJiox_fz8JsoPreX4woW6-EqAebhpJWnKUVLVeGXkrI,10000
5
+ tgwrap/main.py,sha256=Dqqzqe2x7UXHgAgQL764CfoZ6V0JFbk4TphBkBcYcC8,74872
6
+ tgwrap/printer.py,sha256=dkcOCPIPB-IP6pn8QMpa06xlcqPFVaDvxnz-QEpDJV0,2663
7
+ tgwrap-0.8.13.dist-info/LICENSE,sha256=VT-AVxIXt3EQTC-7Hy1uPGnrDNJLqfcgLgJD78fiyx4,1065
8
+ tgwrap-0.8.13.dist-info/METADATA,sha256=EDgJtU6kePAZrn-aft9LWPDImK_5qEYaBoJCGhY_L3s,11616
9
+ tgwrap-0.8.13.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
10
+ tgwrap-0.8.13.dist-info/entry_points.txt,sha256=H8X0PMPmd4aW7Y9iyChZ0Ug6RWGXqhRUvHH-6f6Mxz0,42
11
+ tgwrap-0.8.13.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- tgwrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- tgwrap/analyze.py,sha256=CsSaGv-be6ATy36z9X7x00gpKY59soJys2VbIzD-tmg,8726
3
- tgwrap/cli.py,sha256=weYPXnpZ1200L28tNGzVaO_GlX7wAdLn1nQZsHKpe3k,29060
4
- tgwrap/deploy.py,sha256=oD-H1ojdcSQTbiQHs7BZP9NnbV_MDtlpJFaDRl2-KvU,9578
5
- tgwrap/main.py,sha256=Eocuece85XM5GHF6TA7OdhxHZqTlbSds5gu7lpZQGQA,74459
6
- tgwrap/printer.py,sha256=dkcOCPIPB-IP6pn8QMpa06xlcqPFVaDvxnz-QEpDJV0,2663
7
- tgwrap-0.8.11.dist-info/LICENSE,sha256=VT-AVxIXt3EQTC-7Hy1uPGnrDNJLqfcgLgJD78fiyx4,1065
8
- tgwrap-0.8.11.dist-info/METADATA,sha256=l8Pmnql-7cijxcy8Hysw3Ka54ZQ2vYumjwDTz6TW3eQ,11529
9
- tgwrap-0.8.11.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
10
- tgwrap-0.8.11.dist-info/entry_points.txt,sha256=H8X0PMPmd4aW7Y9iyChZ0Ug6RWGXqhRUvHH-6f6Mxz0,42
11
- tgwrap-0.8.11.dist-info/RECORD,,