tgwrap 0.11.3__py3-none-any.whl → 0.11.5__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
@@ -17,7 +17,7 @@ import click
17
17
 
18
18
  from outdated import check_outdated
19
19
 
20
- from .main import TgWrap
20
+ from .main import TgWrap, TG_COMMANDS, STAGES
21
21
 
22
22
  PACKAGE_NAME = 'tgwrap'
23
23
  try:
@@ -48,11 +48,6 @@ def check_latest_version(verbose=False):
48
48
 
49
49
  CLICK_CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
50
50
 
51
- TG_COMMANDS=[
52
- 'init', 'validate', 'validate-inputs', 'plan', 'apply',
53
- 'destroy', 'info', 'output', 'show', 'state', 'force-unlock'
54
- ]
55
- STAGES=['global', 'sbx', 'dev', 'qas', 'run', 'tst', 'acc', 'prd']
56
51
  class DefaultGroup(click.Group):
57
52
  '''
58
53
  Allow a default command for a group
@@ -245,11 +240,37 @@ def run(command, verbose, debug, dry_run, no_lock, update, upgrade,
245
240
  help=r'A glob of a directory that needs to be excluded, this option can be used multiple times. For example: -E "integrations/\*/\*"',
246
241
  show_default=True,
247
242
  )
243
+ @click.option('--analyze-after-plan', is_flag=True, default=True,
244
+ help='Analyze the results after a plan',
245
+ show_default=True
246
+ )
247
+ @click.option('--analyze-config', '-A', default=None,
248
+ help='Name of the analyze config file (or set TGWRAP_ANALYZE_CONFIG environment variable)',
249
+ envvar='TGWRAP_ANALYZE_CONFIG', type=click.Path(),
250
+ )
251
+ @click.option('--ignore-attributes', '-i',
252
+ multiple=True, default=[],
253
+ help=r'A glob of attributes for which, during plan, updates can be ignored, this option can be used multiple times (or set TGWRAP_ANALYZE_IGNORE environment variable)',
254
+ envvar='TGWRAP_ANALYZE_IGNORE',
255
+ show_default=True
256
+ )
257
+ @click.option('--planfile-dir', '-P', default='.terragrunt-cache/current',
258
+ help='Relative path to directory with plan file (or set TGWRAP_PLANFILE_DIR environment variable), see README for more details',
259
+ envvar='TGWRAP_PLANFILE_DIR', type=click.Path(),
260
+ show_default=True,
261
+ )
262
+ @click.option('--data-collection-endpoint', '-D', default=None,
263
+ help='Optional URI of an (Azure) data collection endpoint, to which the analyse results will be sent',
264
+ envvar='TGWRAP_ANALYZE_DATA_COLLECTION_ENDPOINT',
265
+ show_default=True,
266
+ )
248
267
  @click.argument('terragrunt-args', nargs=-1, type=click.UNPROCESSED)
249
268
  @click.version_option(version=__version__)
250
269
  def run_all(command, verbose, debug, dry_run, no_lock, update, upgrade, exclude_external_dependencies,
251
- step_by_step, continue_on_error, planfile, auto_approve, clean, working_dir, start_at_step,
252
- limit_parallelism, include_dir, exclude_dir, terragrunt_args):
270
+ step_by_step, continue_on_error, planfile, auto_approve, clean, working_dir,
271
+ start_at_step, limit_parallelism, include_dir, exclude_dir,
272
+ analyze_after_plan, analyze_config, ignore_attributes, planfile_dir, data_collection_endpoint,
273
+ terragrunt_args):
253
274
  """ Executes a terragrunt command across multiple projects """
254
275
 
255
276
  check_latest_version(verbose)
@@ -273,6 +294,11 @@ def run_all(command, verbose, debug, dry_run, no_lock, update, upgrade, exclude_
273
294
  limit_parallelism=limit_parallelism,
274
295
  include_dirs=include_dir,
275
296
  exclude_dirs=exclude_dir,
297
+ analyze_after_plan=analyze_after_plan,
298
+ analyze_config=analyze_config,
299
+ ignore_attributes=ignore_attributes,
300
+ planfile_dir=planfile_dir,
301
+ data_collection_endpoint=data_collection_endpoint,
276
302
  terragrunt_args=terragrunt_args,
277
303
  )
278
304
 
@@ -546,8 +572,8 @@ def unlock(module, auto_approve, verbose, dry_run, working_dir):
546
572
  help='Clean up files on target side that do not exist as source',
547
573
  show_default=True
548
574
  )
549
- @click.option('--include-lock-file', '-i', is_flag=True, default=False,
550
- help='Include the terraform lock file',
575
+ @click.option('--include-dotenv-file', '-i', is_flag=True, default=False,
576
+ help='Include the .env (or .envrc) files',
551
577
  show_default=True
552
578
  )
553
579
  @click.option('--auto-approve', '-a', is_flag=True, default=False,
@@ -568,7 +594,7 @@ def unlock(module, auto_approve, verbose, dry_run, working_dir):
568
594
  @click.version_option(version=__version__)
569
595
  def sync(
570
596
  source_domain, source_stage, target_domain, target_stage, module, auto_approve,
571
- verbose, dry_run, clean, include_lock_file, working_dir
597
+ verbose, dry_run, clean, include_dotenv_file, working_dir
572
598
  ):
573
599
  """ Syncs the terragrunt config files from one stage to another (and possibly to a different domain) """
574
600
 
@@ -584,7 +610,7 @@ def sync(
584
610
  auto_approve=auto_approve,
585
611
  dry_run=dry_run,
586
612
  clean=clean,
587
- include_lock_file=include_lock_file,
613
+ include_dotenv_file=include_dotenv_file,
588
614
  working_dir=working_dir,
589
615
  )
590
616
 
@@ -604,8 +630,8 @@ def sync(
604
630
  help='Clean up files on target side that do not exist as source',
605
631
  show_default=True
606
632
  )
607
- @click.option('--include-lock-file', '-i', is_flag=True, default=False,
608
- help='Include the terraform lock file',
633
+ @click.option('--include-dotenv-file', '-i', is_flag=True, default=False,
634
+ help='Include the .env (or .envrc) files',
609
635
  show_default=True
610
636
  )
611
637
  @click.option('--auto-approve', '-a', is_flag=True, default=False,
@@ -626,7 +652,7 @@ def sync(
626
652
  @click.version_option(version=__version__)
627
653
  def sync_dir(
628
654
  source_directory, target_directory, auto_approve,
629
- verbose, dry_run, clean, include_lock_file, working_dir
655
+ verbose, dry_run, clean, include_dotenv_file, working_dir
630
656
  ):
631
657
  """ Syncs the terragrunt config files from one directory to anothery """
632
658
 
@@ -639,7 +665,7 @@ def sync_dir(
639
665
  auto_approve=auto_approve,
640
666
  dry_run=dry_run,
641
667
  clean=clean,
642
- include_lock_file=include_lock_file,
668
+ include_dotenv_file=include_dotenv_file,
643
669
  working_dir=working_dir,
644
670
  )
645
671
 
tgwrap/deploy.py CHANGED
@@ -10,7 +10,7 @@ import shlex
10
10
  from .printer import Printer
11
11
 
12
12
  def run_sync(
13
- source_path, target_path, auto_approve, include_lock_file, dry_run, clean, terragrunt_file,
13
+ source_path, target_path, auto_approve, include_dotenv_file, dry_run, clean, terragrunt_file,
14
14
  excludes=[], source_stage=None, target_stage=None, source_domain=None, verbose=False,
15
15
  ):
16
16
  """ Run a sync copying files from a source to a target path """
@@ -46,8 +46,8 @@ def run_sync(
46
46
  dry_run_stmt = '--dry-run' if dry_run else ''
47
47
  clean_stmt = '--delete' if clean else ''
48
48
  env_file_stmt = "--exclude='env.hcl'" if source_stage != target_stage else "--include='env.hcl'"
49
- lock_file_stmt = "--include='.terraform.lock.hcl'" if include_lock_file \
50
- else "--exclude='.terraform.lock.hcl'"
49
+ dotenv_file_stmt = "--include='.env' --include='.envrc'" if include_dotenv_file \
50
+ else "--exclude='.env' --exclude='.envrc'"
51
51
  excludes_stmt = ' '.join([f"--exclude={x}" for x in excludes])
52
52
 
53
53
  include_statements = ""
@@ -57,7 +57,7 @@ def run_sync(
57
57
  #
58
58
  if os.path.isdir(target_path):
59
59
  include_statements = \
60
- f"--include='{terragrunt_file}' {lock_file_stmt} {env_file_stmt} {excludes_stmt} " + \
60
+ f"--include='{terragrunt_file}' {dotenv_file_stmt} {env_file_stmt} {excludes_stmt} " + \
61
61
  "--exclude='.terragrunt-cache/' --exclude='.terraform/' " + \
62
62
  "--exclude='terragrunt-debug.tfvars.json' --exclude=planfile " + \
63
63
  "--exclude='.DS_Store' --exclude='*.log' "
tgwrap/main.py CHANGED
@@ -38,6 +38,35 @@ from .analyze import run_analyze
38
38
  from .deploy import prepare_deploy_config, run_sync
39
39
  from .inspector import AzureInspector
40
40
 
41
+ TG_COMMANDS=[
42
+ 'apply',
43
+ 'destroy',
44
+ 'exec',
45
+ 'force-unlock',
46
+ 'graph',
47
+ 'info',
48
+ 'init',
49
+ 'output',
50
+ 'plan',
51
+ 'run',
52
+ 'show',
53
+ 'state',
54
+ 'taint',
55
+ 'untaint',
56
+ 'validate-inputs',
57
+ 'validate',
58
+ ]
59
+ STAGES=[
60
+ 'global',
61
+ 'sbx',
62
+ 'dev',
63
+ 'qas',
64
+ 'run',
65
+ 'tst',
66
+ 'acc',
67
+ 'prd',
68
+ ]
69
+
41
70
  class DateTimeEncoder(json.JSONEncoder):
42
71
  def default(self, obj):
43
72
  if isinstance(obj, datetime):
@@ -1095,8 +1124,10 @@ class TgWrap():
1095
1124
  sys.exit(rc.returncode)
1096
1125
 
1097
1126
  def run_all(self, command, debug, dry_run, no_lock, update, upgrade,
1098
- exclude_external_dependencies, step_by_step, continue_on_error, planfile, auto_approve, clean,
1099
- working_dir, start_at_step, limit_parallelism, include_dirs, exclude_dirs, terragrunt_args):
1127
+ exclude_external_dependencies, step_by_step, continue_on_error, planfile, auto_approve,
1128
+ clean, working_dir, start_at_step, limit_parallelism, include_dirs, exclude_dirs,
1129
+ analyze_after_plan, analyze_config, ignore_attributes, planfile_dir, data_collection_endpoint,
1130
+ terragrunt_args):
1100
1131
  """ Executes a terragrunt command across multiple modules """
1101
1132
 
1102
1133
  self.printer.verbose(f"Attempting to execute 'run-all {command}'")
@@ -1128,6 +1159,7 @@ class TgWrap():
1128
1159
  if clean and not dry_run:
1129
1160
  self.clean(working_dir=working_dir)
1130
1161
 
1162
+ rc = None
1131
1163
  if step_by_step:
1132
1164
  self.printer.verbose(
1133
1165
  f'This command will be executed for each individual module:\n$ {cmd}'
@@ -1154,9 +1186,29 @@ class TgWrap():
1154
1186
  self.printer.warning('In dry run mode, no real actions are executed!!')
1155
1187
  else:
1156
1188
  rc = subprocess.run(shlex.split(cmd))
1189
+
1157
1190
  self.printer.verbose(rc)
1158
1191
 
1159
- sys.exit(rc.returncode)
1192
+ # if we are planning, and analyze is requested, we need to run the analysis
1193
+ if analyze_after_plan and command.lower() == 'plan':
1194
+ self.printer.verbose('Analyze after plan requested')
1195
+ self.analyze(
1196
+ exclude_external_dependencies=exclude_external_dependencies,
1197
+ working_dir=working_dir,
1198
+ start_at_step=0,
1199
+ out=None,
1200
+ parallel_execution=None,
1201
+ analyze_config=analyze_config,
1202
+ ignore_attributes=ignore_attributes,
1203
+ include_dirs=include_dirs,
1204
+ exclude_dirs=exclude_dirs,
1205
+ planfile_dir=planfile_dir,
1206
+ data_collection_endpoint=data_collection_endpoint,
1207
+ terragrunt_args=terragrunt_args,
1208
+ )
1209
+
1210
+ if rc:
1211
+ sys.exit(rc.returncode)
1160
1212
 
1161
1213
  def run_import(self, address, id, dry_run, working_dir, no_lock, terragrunt_args):
1162
1214
  """ Executes the terragrunt/terraform import command """
@@ -1241,7 +1293,9 @@ class TgWrap():
1241
1293
  else:
1242
1294
  self.printer.verbose('Use native terraform for module selection')
1243
1295
  use_native_terraform = True
1244
- cmd = f"terraform show -json {self.PLANFILE_NAME}"
1296
+ # the 'tf' command is part of tenv and determines, based on the existence of .opentofu-version or .terraform-version
1297
+ # whether to use terraform or opentofu
1298
+ cmd = f"tf show -json {self.PLANFILE_NAME}"
1245
1299
 
1246
1300
  config = None
1247
1301
  if analyze_config:
@@ -1448,7 +1502,7 @@ class TgWrap():
1448
1502
 
1449
1503
  def sync(
1450
1504
  self, source_stage, target_stage, source_domain, target_domain, module,
1451
- auto_approve, dry_run, clean, include_lock_file, working_dir,
1505
+ auto_approve, dry_run, clean, include_dotenv_file, working_dir,
1452
1506
  ):
1453
1507
  """ Syncs the terragrunt config files from one stage to another (and possibly to a different domain) """
1454
1508
 
@@ -1476,7 +1530,7 @@ class TgWrap():
1476
1530
  source_domain=source_domain,
1477
1531
  source_stage=source_stage,
1478
1532
  target_stage=target_stage,
1479
- include_lock_file=include_lock_file,
1533
+ include_dotenv_file=include_dotenv_file,
1480
1534
  auto_approve=auto_approve,
1481
1535
  dry_run=dry_run,
1482
1536
  clean=clean,
@@ -1486,7 +1540,7 @@ class TgWrap():
1486
1540
 
1487
1541
  def sync_dir(
1488
1542
  self, source_directory, target_directory,
1489
- auto_approve, dry_run, clean, include_lock_file, working_dir,
1543
+ auto_approve, dry_run, clean, include_dotenv_file, working_dir,
1490
1544
  ):
1491
1545
  """ Syncs the terragrunt config files from one directory to anothery """
1492
1546
 
@@ -1499,10 +1553,10 @@ class TgWrap():
1499
1553
  run_sync(
1500
1554
  source_path=source_path,
1501
1555
  target_path=target_path,
1502
- include_lock_file=include_lock_file,
1503
1556
  auto_approve=auto_approve,
1504
1557
  dry_run=dry_run,
1505
1558
  clean=clean,
1559
+ include_dotenv_file=include_dotenv_file,
1506
1560
  terragrunt_file=self.TERRAGRUNT_FILE,
1507
1561
  verbose=self.printer.print_verbose,
1508
1562
  )
@@ -1590,6 +1644,7 @@ class TgWrap():
1590
1644
  source_path = os.path.join(
1591
1645
  source_dir, global_config['source']
1592
1646
  )
1647
+
1593
1648
  target = global_config.get('target', global_config['source'])
1594
1649
  target_path = os.path.dirname(
1595
1650
  os.path.join(
@@ -1625,7 +1680,7 @@ class TgWrap():
1625
1680
  source_path=value['source'],
1626
1681
  target_path=value['target'],
1627
1682
  excludes=value.get('excludes', []),
1628
- include_lock_file=True,
1683
+ include_dotenv_file=True,
1629
1684
  auto_approve=True,
1630
1685
  dry_run=dry_run,
1631
1686
  clean=False,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: tgwrap
3
- Version: 0.11.3
3
+ Version: 0.11.5
4
4
  Summary: A (terragrunt) wrapper around a (terraform) wrapper around ....
5
5
  License: MIT
6
6
  Keywords: terraform,terragrunt,terrasafe,python
@@ -145,7 +145,7 @@ export TGWRAP_PLANFILE_DIR=".terragrunt-cache/current"
145
145
 
146
146
  Or pass it along with the `--planfile-dir|-P` option and it will use that.
147
147
 
148
- ### Logging the results
148
+ ## 5. Logging the results
149
149
 
150
150
  `tgwrap` supports logging the analyze results to an [Azure Log Analytics](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-overview) custom table.
151
151
 
@@ -214,7 +214,15 @@ The log analytics (custom) table should have a schema that is able to cope with
214
214
  | unknown | Int |
215
215
  | updates | Int |
216
216
 
217
- ## More than a wrapper
217
+ # Terraform vs OpenTofu
218
+
219
+ `tgwrap` is compatible with both [terraform](https://www.terraform.io/) and [opentofu](https://opentofu.org/).
220
+
221
+ As we are **almost** exclusively relying on `terragrunt` to invoke them on our behalf, there is not much exposure to that decision.
222
+
223
+ However, there is one exception (and maybe there will be more in the future) where this is not true. For that we rely on the existance of the `tf` binary (or symbolic link, or alias and so on). It is managed automatically by the [tenv tool](https://github.com/tofuutils/tenv) (higly recommended!), but if you don't want to use it, make sure you create an alias or something like that.
224
+
225
+ # More than a wrapper
218
226
 
219
227
  Over time, tgwrap became more than a wrapper, blantly violating [#1 of the unix philosophy](https://en.wikipedia.org/wiki/Unix_philosophy#:~:text=The%20Unix%20philosophy%20is%20documented,%2C%20as%20yet%20unknown%2C%20program.): 'Make each program do one thing well'.
220
228
 
@@ -0,0 +1,13 @@
1
+ tgwrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ tgwrap/analyze.py,sha256=fuET7_L8b6rlv2qnKOYXSlIv5LArvZ39CPktCeQHo7A,10285
3
+ tgwrap/cli.py,sha256=7VPowJ9DtbtbOsl5s01Vj0rsMTQuin5KhvOTbNTSQZA,33551
4
+ tgwrap/deploy.py,sha256=gwFdRDzJWjkmEzZ8b0GUSrgvJeOKnpnJm1c6mM80OcY,10259
5
+ tgwrap/inspector-resources-template.yml,sha256=Mos8NDzzZ3VxdXgeiVL9cmQfRcIXIHMLf79_KLwdXu8,3297
6
+ tgwrap/inspector.py,sha256=5pW7Ex1lkKRoXY6hZGbCNmSD2iRzgMSfqi9w7gb-AcY,16990
7
+ tgwrap/main.py,sha256=mPVVWJC_zXz9ozWwvkBGrWn8t9hytI812pCh5zxRd44,95862
8
+ tgwrap/printer.py,sha256=frn1PARd8A28mkRCYR6ybN2x0NBULhNOutn4l2U7REY,2754
9
+ tgwrap-0.11.5.dist-info/LICENSE,sha256=VT-AVxIXt3EQTC-7Hy1uPGnrDNJLqfcgLgJD78fiyx4,1065
10
+ tgwrap-0.11.5.dist-info/METADATA,sha256=QvyHQUE4WALyXsMIxnN-qRubvp33iv4JxhzS3knJkdw,18355
11
+ tgwrap-0.11.5.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
12
+ tgwrap-0.11.5.dist-info/entry_points.txt,sha256=H8X0PMPmd4aW7Y9iyChZ0Ug6RWGXqhRUvHH-6f6Mxz0,42
13
+ tgwrap-0.11.5.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- tgwrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- tgwrap/analyze.py,sha256=fuET7_L8b6rlv2qnKOYXSlIv5LArvZ39CPktCeQHo7A,10285
3
- tgwrap/cli.py,sha256=9zvBop3Gm5B2b8K-zXVHEvRUCC7t5ffZUJZEpj1Fxtw,32186
4
- tgwrap/deploy.py,sha256=-fSk-Ix_HqrXY7KQX_L27TnFzIuhBHYv4xYJW6zRDN4,10243
5
- tgwrap/inspector-resources-template.yml,sha256=Mos8NDzzZ3VxdXgeiVL9cmQfRcIXIHMLf79_KLwdXu8,3297
6
- tgwrap/inspector.py,sha256=5pW7Ex1lkKRoXY6hZGbCNmSD2iRzgMSfqi9w7gb-AcY,16990
7
- tgwrap/main.py,sha256=Vzmy9GEuBT-T6TB_ZzN_lET5ihY0P-9WcPZ_MPCus_k,94366
8
- tgwrap/printer.py,sha256=frn1PARd8A28mkRCYR6ybN2x0NBULhNOutn4l2U7REY,2754
9
- tgwrap-0.11.3.dist-info/LICENSE,sha256=VT-AVxIXt3EQTC-7Hy1uPGnrDNJLqfcgLgJD78fiyx4,1065
10
- tgwrap-0.11.3.dist-info/METADATA,sha256=Q2KwR6I-XkakoBPf2PCchgR5xlcOJJaG0In4V6DedJ8,17698
11
- tgwrap-0.11.3.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
12
- tgwrap-0.11.3.dist-info/entry_points.txt,sha256=H8X0PMPmd4aW7Y9iyChZ0Ug6RWGXqhRUvHH-6f6Mxz0,42
13
- tgwrap-0.11.3.dist-info/RECORD,,