tgwrap 0.8.6__tar.gz → 0.8.8__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tgwrap
3
- Version: 0.8.6
3
+ Version: 0.8.8
4
4
  Summary: A (terragrunt) wrapper around a (terraform) wrapper around ....
5
5
  Home-page: https://gitlab.com/lunadata/tgwrap
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "tgwrap"
3
- version = "0.8.6"
3
+ version = "0.8.8"
4
4
  description = "A (terragrunt) wrapper around a (terraform) wrapper around ...."
5
5
  authors = ["Gerco Grandia <gerco.grandia@4synergy.nl>", "Pascal Alma <pascal.alma@4synergy.nl>"]
6
6
  license = "MIT"
@@ -23,7 +23,7 @@ entry_points = \
23
23
 
24
24
  setup_kwargs = {
25
25
  'name': 'tgwrap',
26
- 'version': '0.8.6',
26
+ 'version': '0.8.8',
27
27
  'description': 'A (terragrunt) wrapper around a (terraform) wrapper around ....',
28
28
  'long_description': '# tg-wrap\n\nThis app simply wraps terragrunt (which is a wrapper around terraform, which is a wrapper around cloud APIs, which is...).\n\nWait, why on earth do we need a wrapper for a wrapper (for a wrapper)?\n\nWell, first of all it is pretty opinionated so what works for us, doesn\'t necessarily work for you.\n\nBut our reasoning for creating this is as follows:\n\n## 1. Less typing\n\nterraform is great, and in combination with terragrunt even greater! But let\'s face it, terragrunt does not excel in conciseness! The options are pretty long, which leads to lots of typing. We don\'t like typing!\n\n## 2. Testing modules locally\n\nHowever, more importantly, we are heavily utilising [TERRAGRUNT_SOURCE](https://terragrunt.gruntwork.io/docs/features/execute-terraform-commands-on-multiple-modules-at-once/#testing-multiple-modules-locally) when developing.\n\nThe thing is that as long as you use `run-all` you can use one setting for that variable (and conveniently set it as an environment variable), while if you run a regular command, you need to specify the full path. Which is obviously different for each project.\n\nWhich leads to (even) more typing, and worse: a higher chance for errors.\n\nLuckily you can use `run-all` and add the appriopriate flags to ensure it behaves like a regular plan|apply|destroy etc. But again, more typing.\n\nNothing a [bunch a aliases](https://gitlab.com/lunadata/terragrunt-utils/-/blob/main/tg-shell.sh) can\'t solve though!\n\n## 3. But the original reason was: Errors when using run-all are challenging\n\nOne of the main boons of terragrunt is the ability to break up large projects in smaller steps while still retaining the inter-dependencies. However, when working on such a large project and something goes wrong somewhere in the middle is pretty challenging.\n\nterragrunt\'s error messages are pretty massive, and this is extrapolated with every individual project in your dependency chain.\n\nAnd if it fails somewhere at the front, it keeps on trying until the last one, blowing up your terminal in the process.\n\nSo we wanted a possibility to run the projects step by step, using the dependency graph of terragrunt and have a bit more control over it.\n\nAnd when you can run it step by step, you can make the process also re-startable, which is also pretty handy!\n\nAnd this was not something a bunch of aliases could solve, hence this wrapper was born. And while we we\'re at it, replacing the aliases with this was then pretty straightforward next step as well.\n\n## 4. Analyzing plan files\n\nWhen using the run-all, analyzing what is about to be changed is not going to be easier. Hence we created the `tgwrap analyze` function that lists all the planned changes and (if a config file is availabe) calculates a drift score and runs a [terrasafe](https://pypi.org/project/terrasafe/) style validation check.\n\nIt needs a config file as follows:\n\n```yaml\n---\n#\n# Critically of resources as interpreted by \'tgwrap analyze\'.\n# It uses it for a \'terrasafe\' like validation if resources can safely be deleted.\n# On top of that it tries to analyze and quantify the drift impact of the changes,\n# so that this can be monitored.\n#\nlow:\n # defaults:\n # terrasafe_level: ignore_deletions\n # drift_impact:\n # default: minor\n # delete: medium\n azuread_application.: {} # if we you want to use the defaults\n azuread_app_role_assignment: # or if you want to override these\n drift_impact:\n delete: minor\n # and so on, and so forth\nmedium:\n # defaults:\n # terrasafe_level: ignore_deletion_if_recreation\n # drift_impact:\n # default: medium\n # delete: major\n azurerm_data_factory_linked_service_key_vault.: {}\n # and so on, and so forth\nhigh:\n # defaults:\n # terrasafe_level: unauthorized_deletion\n # drift_impact:\n # default: major\n # update: medium\n azuread_group.:\n drift_impact:\n create: minor\n update: minor\n azurerm_application_insights.: {}\n # and so on, and so forth\n```\n\n### Speeding up the performance of analyze\n\nThis `analyze` function turned out to be pretty slow, where most of the time went into the `terragrunt show` function that is executed for each individual module. \n\nThis was a bit surprising as the plan file is already available on the file system, but it turns out that terragrunt is taking quite a bit of time for managing the depdencies. Even when you\'re excluding the external dependencies and are located in a particular module.\n\nSo, if you add the following to your root `terragrunt.hcl`:\n\n```hcl\nterraform {\n after_hook "link_to_current_module" {\n commands = ["init", "plan", "apply", "validate", "destroy"]\n execute = ["bash", "-c", "ln -sf $(pwd) ${get_terragrunt_dir()}/.terragrunt-cache/current"]\n }\n}\n```\n\nThe directory where the plan file is stored (including the other resources that terraform needs) becomes predictable and it becomes possible to run a native `terraform show` (instead `terragrunt show`) which dramatically speed up things.\n\nJust set the proper value as an environment variable:\n\n```console\nexport TGWRAP_PLANFILE_DIR=".terragrunt-cache/current"\n```\n\nOr pass it along with the `--planfile-dir|-P` option and it will use that.\n\n## More than a wrapper\n\nOver 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\'.\n\nFor instance, the \'analyze\' functionality is already an example, but more features such as deploying a landing zone has crept into the application. It makes sense for how we\'re using it, but we\'re fully aware this makes it less generically applicable.\n\n## Usage\n\n```console\n# general help\ntgwrap --help\n\ntgwrap run -h\ntgwrap run-all -h\n\n# run a plan\ntgwrap plan # which is the same as tgwrap run plan\n\n# run-all a plan\ntgwrap run-all plan\n\n# run-all with excluding a particular directory\ntgwrap run-all plan -E \'excluded-dir/*\'\n# or a directory somewhere further down in the path\ntgwrap run-all plan -E \'**/excluded-dir/**\'\n\n# or do the same in step-by-step mode\ntgwrap run-all plan -s\n\n# or excluding (aka ignoring) external dependencies\ntgwrap run-all plan -sx\n\n# if you want to add additional arguments it is recommended to use -- as separator (although it *might* work without)\ntgwrap output -- -json\n```\n\n> Note: special precautions are needed when passing on parameters that contain quotes. For instance, if you want to move state like below, escape the double quote in the staate address:\n\n`tgwrap state mv \'azuread_group.this[\\"viewers\\"]\' \'azuread_group.this[\\"readers\\"]\'`\n\n## A word about escaping inputs\n\nYour shell is escaping special characters such as `*` and `"` before passing it to the program (`tgwrap` in this case). So some inputs **need** to be escaped in order to function properly.\n\nFor example:\n\n```console\n# to exclude certain modules from an action (such as analyze):\ntgwrap analyze -E \'integrations/\\*/\\*\'\n\n# to import a resource that has a " in its address:\ntgwrap import -a \'azuread_group.this[\\"my_group\\"]\' -i ${GROUP_ID}\n```\n\n## Deploy manifests\n\nIn order to easily deploy a new version of the terraform (and associated terragrunt) modules, we include a manifest file in the root of the landing zone:\n\n```yaml\n---\ngit_repository: ssh://git@gitlab.com/my-org/my-terraform-modules-repo.git\nbase_path: terragrunt/my-platform # path where the terragrunt modules are stored \nconfig_path: terragrunt/config/platform-dev # path (relative to base path) where the config files are stored\n\ndeploy: # which modules do you want to deploy\n dtap:\n applies_to_stages:\n - dev\n - tst\n - acc\n - prd\n source_stage: dev\n source_dir: platform # optional, if the source modules are not directly in the stage dir, but in <stage>/<source_dir> directory\n base_dir: platform # optional, if you want to deploy the base modules in its own dir, side by side with substacks\n config_dir: ../../../config # this is relative to where you run the deploy\n configs:\n - my-config.hcl\n - ../my-ss-config-dir\n # skip_all_modules: True # could be handy if you only want to deploy substacks and no regular modules\n exclude_modules: # these modules will always be excluded, can be omitted\n - my-specific-module\n include_modules: {} # omit or use an empty dict for all of them\n # or specify your modules as follows\n # base: {} # just a simple include\n # networking-connected: # or a bit more complicated\n # - source: networking\n # - target: networking-connected\n\nsub_stacks:\n is01:\n source: shared-integration/intsvc01\n target: integration/is01\n exclude_modules: # a list of modules that will always be excluded, can be omitted\n - my-specific-module\n configs:\n - my-ss-config.hcl\n - ../my-ss-config-dir\n is02:\n source: shared-integration/intsvc01\n target: integration/is02\n\n# global configuration files that are deployed as well\n# note that these files are typically applicable to all landing zones and stages!\n# this might lead to unexpected behaviour\n# note that you can exclude syncing these files with a command line switch\nglobal_config_files:\n root-terragrunt:\n source: ../../terragrunt.hcl # relative to base_path\n target: ../../terragrunt.hcl # can be omitted, then it is same as source path\n terrasafe-config:\n source: ../../terrasafe-config.json\n```\n\n## Generating change logs\n\ntgwrap can generate a change log by running:\n\n```console\ntgwrap change-log [--changelog-file ./CHANGELOG.md]\n```\n\nThe (optional) change log file will be, if passed along. tgwrap then checks the file for the existance of a start and end markers, in the following format:\n\n```python\n start_marker = \'<!-- BEGINNING OF OF TGWRAP CHANGELOG SECTION -->\'\n end_marker = \'<!-- END OF TGWRAP CHANGELOG SECTION -->\'\n```\n\nIf they exist, everything between these lines will be replaced by the new change log.\n\n## Development\n\nIn order to develop, you need to apply it to your terragrunt projects. For that you can use the `--terragrunt-working-dir` option and just run it from the poetry directory. Alternatively you can use the [tgwrap-dev](./tgwrap-dev) script and invoke that from your terragrunt directories. Either put it in your `PATH` or create an alias for convenience.\n',
29
29
  'author': 'Gerco Grandia',
@@ -391,48 +391,53 @@ class TgWrap():
391
391
  # remove empty strings
392
392
  tags = [tag for tag in tags if tag != ""]
393
393
 
394
- if not tags:
395
- raise Exception('Could not load tags in source repository, cannot deploy without it.')
394
+ tags.insert(0, self.LATEST_VERSION)
396
395
 
397
396
  return tags
398
397
 
399
398
  def check_version_tag(reference, working_dir):
399
+ is_latest = (reference == self.LATEST_VERSION)
400
400
  is_branch = False
401
- quiet_mode = "" if self.printer.print_verbose else "--quiet"
402
-
403
- # Check if the given reference is a tag
404
- tag_command = f'git show-ref --verify {quiet_mode} refs/tags/{reference}'
405
- tag_process = subprocess.run(
406
- shlex.split(tag_command),
407
- cwd=working_dir,
408
- capture_output=True,
409
- )
410
- is_tag = tag_process.returncode == 0
411
- self.printer.verbose(f'Check for tag: {tag_process}')
412
-
413
- # if it is not a tag, then it might be a branch
414
- if not is_tag:
415
- branch_command = f'git switch {reference}'
416
- branch_process = subprocess.run(
417
- shlex.split(branch_command),
401
+ is_tag = False
402
+
403
+ if not is_latest:
404
+ quiet_mode = "" if self.printer.print_verbose else "--quiet"
405
+
406
+ # Check if the given reference is a tag
407
+ tag_command = f'git show-ref --verify {quiet_mode} refs/tags/{reference}'
408
+ tag_process = subprocess.run(
409
+ shlex.split(tag_command),
418
410
  cwd=working_dir,
419
411
  capture_output=True,
420
412
  )
421
- is_branch = branch_process.returncode == 0
422
- self.printer.verbose(f'Check for branch: {branch_process}')
413
+ is_tag = tag_process.returncode == 0
414
+ self.printer.verbose(f'Check for tag: {tag_process}')
415
+
416
+ # if it is not a tag, then it might be a branch
417
+ if not is_tag:
418
+ branch_command = f'git switch {reference}'
419
+ branch_process = subprocess.run(
420
+ shlex.split(branch_command),
421
+ cwd=working_dir,
422
+ capture_output=True,
423
+ )
424
+ is_branch = branch_process.returncode == 0
425
+ self.printer.verbose(f'Check for branch: {branch_process}')
423
426
 
424
427
  # Print the result
425
- if is_branch:
428
+ if is_latest:
429
+ self.printer.verbose(f"The given reference '{reference}' is the latest version.")
430
+ elif is_branch:
426
431
  self.printer.verbose(f"The given reference '{reference}' is a branch.")
427
432
  elif is_tag:
428
433
  self.printer.verbose(f"The given reference '{reference}' is a tag.")
429
434
  else:
430
- msg = f"The given reference '{reference}' is neither a branch nor a tag."
431
- self.printer.verbose(f"The given reference '{reference}' is neither a branch nor a tag.")
435
+ msg = f"The given reference '{reference}' is neither latest, a branch nor a tag."
436
+ self.printer.verbose(msg)
432
437
  raise Exception(msg)
433
438
 
434
439
 
435
- return is_branch, is_tag
440
+ return is_latest, is_branch, is_tag
436
441
 
437
442
  # clone the repo
438
443
  repo = manifest['git_repository']
@@ -461,14 +466,16 @@ class TgWrap():
461
466
  self.printer.normal
462
467
 
463
468
  # first check if we have a tag or a branch
464
- is_branch, is_tag = check_version_tag(
469
+ is_latest, is_branch, is_tag = check_version_tag(
465
470
  reference=version_tag,
466
471
  working_dir=target_dir,
467
472
  )
468
473
 
469
474
  self.printer.header(f'Deploy using reference {version_tag}')
470
475
 
471
- if is_tag:
476
+ if is_latest:
477
+ pass # nothing to do, we already have latest
478
+ elif is_tag:
472
479
  cmd = f"git checkout -b source {version_tag}"
473
480
  rc = subprocess.run(
474
481
  shlex.split(cmd),
@@ -844,15 +851,20 @@ class TgWrap():
844
851
  if not planfile_dir:
845
852
  self.printer.verbose('Use terragrunt for showing plan')
846
853
 
847
- if json and '-json' not in terragrunt_args:
848
- terragrunt_args.append('-json')
854
+ # some environments raise the error: AttributeError: ‘tuple’ object has no attribute ‘append’
855
+ # so convert to a list to make it updateable
856
+ tg_args_list = list(terragrunt_args)
857
+
858
+ # first run a 'show' and write output to file
859
+ if '-json' not in tg_args_list:
860
+ tg_args_list.append('-json')
849
861
 
850
862
  cmd = self._construct_command(
851
863
  command='show',
852
864
  allow_no_run_all=True,
853
865
  exclude_external_dependencies=True,
854
866
  debug=False,
855
- terragrunt_args=terragrunt_args,
867
+ terragrunt_args=tg_args_list,
856
868
  )
857
869
  else:
858
870
  cwd = os.path.join(cwd, planfile_dir)
@@ -998,16 +1010,21 @@ class TgWrap():
998
1010
  self.printer.verbose('Use terragrunt for module selection')
999
1011
 
1000
1012
  use_native_terraform = False
1013
+
1014
+ # some environments raise the error: AttributeError: ‘tuple’ object has no attribute ‘append’
1015
+ # so convert to a list to make it updateable
1016
+ tg_args_list = list(terragrunt_args)
1017
+
1001
1018
  # first run a 'show' and write output to file
1002
- if '-json' not in terragrunt_args:
1003
- terragrunt_args.append('-json')
1019
+ if '-json' not in tg_args_list:
1020
+ tg_args_list.append('-json')
1004
1021
 
1005
1022
  cmd = self._construct_command(
1006
1023
  command='show',
1007
1024
  allow_no_run_all=True,
1008
1025
  exclude_external_dependencies=True,
1009
1026
  debug=False,
1010
- terragrunt_args=terragrunt_args,
1027
+ terragrunt_args=tg_args_list,
1011
1028
  )
1012
1029
  else:
1013
1030
  self.printer.verbose('Use native terraform for module selection')
@@ -1624,12 +1641,16 @@ Note:
1624
1641
 
1625
1642
  current_release = None
1626
1643
  for entry in commit_entries:
1627
- # Check if the entry contains a release tag
1644
+ # Check if the entry contains a release tag, it has a format like this:
1645
+ # 80cbe7a (HEAD -> main, tag: v2023.11.27.1, origin/main, origin/HEAD) Update readme
1628
1646
  match = re.search(release_pattern, entry)
1629
1647
  if match:
1630
1648
  current_release = match.group(1)
1631
1649
  if current_release not in release_commits:
1632
- release_commits[current_release] = []
1650
+ # remove the part between ()
1651
+ pattern = re.compile('\(.*?\) ')
1652
+ updated_entry = pattern.sub('', entry)
1653
+ release_commits[current_release] = [updated_entry]
1633
1654
  elif current_release:
1634
1655
  release_commits[current_release].append(entry)
1635
1656
  # Print the grouped commits to the console (you can write them to a file as needed)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes