Skip to content

Commit 6e11fbb

Browse files
authored
Merge pull request #68 from QualiSystemsLab/feature/update_auto_tags
updated auto tag terraform logic
2 parents d6de1ff + b88b154 commit 6e11fbb

File tree

3 files changed

+57
-86
lines changed

3 files changed

+57
-86
lines changed

package/cloudshell/iac/terraform/services/tf_proc_exec.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def __init__(self, shell_helper: ShellHelperObject, sb_data_handler: SandboxData
3434

3535
def init_terraform(self):
3636
self._shell_helper.logger.info("Performing Terraform Init...")
37-
self._shell_helper.sandbox_messages.write_message("Running Terraform Init...")
37+
self._shell_helper.sandbox_messages.write_message("running Terraform Init...")
3838

3939
self._backend_handler.generate_backend_cfg_file()
4040
backend_config_vars = self._backend_handler.get_backend_secret_vars()
@@ -117,7 +117,8 @@ def tag_terraform(self) -> None:
117117

118118
terraform_version = self._shell_helper.attr_handler.get_attribute(ATTRIBUTE_NAMES.TERRAFORM_VERSION)
119119

120-
start_tagging_terraform_resources(self._tf_working_dir, self._shell_helper.logger, tags_dict, inputs_dict, terraform_version)
120+
start_tagging_terraform_resources(self._tf_working_dir, self._shell_helper.logger, tags_dict, inputs_dict,
121+
terraform_version)
121122
self._set_service_status("Progress 40", "Tagging Passed")
122123
except Exception:
123124
self._set_service_status("Offline", "Tagging Failed")

package/cloudshell/iac/terraform/tagging/tag_terraform_resources.py

Lines changed: 53 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,21 @@
33
# tags changes
44

55
# - remove/comment out main (only uses start_tagging_terraform_resources function)
6+
# - removed Settings class and related methods
67
# - add "from cloudshell.iac.terraform.models.exceptions import TerraformAutoTagsError"
78
# - verify imports are the same (need to add to dependencies file if different and require specific version)
8-
# - modify method definition params
99
# - modify logger to use logger from module
1010
# - _perform_terraform_init_plan is heavily changed due to the fact we may need to run this on windows or linux
1111

1212
# modified methods:
13-
14-
# - _perform_terraform_init_plan (x2)
15-
# - start_tagging_terraform_resources
1613
# - init_logging
14+
# - start_tagging_terraform_resources
15+
# - _perform_terraform_init_plan
1716
# - OverrideTagsTemplatesCreator
1817

19-
18+
import argparse
2019
import re
2120
import enum
22-
import sys
2321
import traceback
2422
from typing import List
2523
import hcl2
@@ -33,39 +31,26 @@
3331
### Added
3432
from cloudshell.iac.terraform.models.exceptions import TerraformAutoTagsError
3533

34+
3635
# =====================================================================================================================
3736

38-
#commented out some contants and methods but isnt required
3937
class Constants:
4038
TAGS = "tags" # used for tag aws and azure resources in terraform
4139
LABELS = "labels" # used for tag kubernetes resources in terraform
42-
# TORQUE_VARIABLES_FILE_NAME = "variables.torque.tfvars"
43-
# TORQUE_TAGS_FILE_NAME = "torque_tags.json"
4440
OVERRIDE_LOG_FILE_NAME = "override_log"
45-
EXCLUDE_FROM_TAGGING_FILE_NAME = "exclude_from_tagging.json"
46-
# TERRAFORM_FOLDER_NAME = "terraform"
47-
48-
# @staticmethod
49-
# def get_tfs_folder_path(main_folder: str):
50-
# return os.path.join(main_folder,
51-
# Constants.TERRAFORM_FOLDER_NAME)
52-
53-
# @staticmethod
54-
# def get_torque_tags_path(main_folder: str):
55-
# return os.path.join(main_folder,
56-
# Constants.TORQUE_TAGS_FILE_NAME)
41+
EXCLUDE_FROM_TAGGING_FILE_NAME = "exclude_from_tagging.json" # modified
5742

5843
@staticmethod
5944
def get_override_log_path(main_folder: str):
6045
return os.path.join(main_folder,
6146
Constants.OVERRIDE_LOG_FILE_NAME)
6247

48+
# modified
6349
@staticmethod
6450
def get_exclude_from_tagging_file_path(main_folder: str):
6551
return os.path.join(main_folder,
6652
Constants.EXCLUDE_FROM_TAGGING_FILE_NAME)
6753

68-
6954
# =====================================================================================================================
7055

7156

@@ -102,48 +87,39 @@ def wrapper(*args, **kwargs):
10287

10388
# =====================================================================================================================
10489

105-
#modified
90+
# modified
10691
class LoggerHelper:
10792
log_instance = None
10893

10994
@staticmethod
11095
def init_logging(logger: logging.Logger):
11196
LoggerHelper.log_instance = logger
11297

113-
@staticmethod
114-
def actual_write(log_type: str, logger: logging.Logger, msg: str, code_line: int = None):
115-
try:
116-
if code_line is None:
117-
caller = getframeinfo(stack()[1][0])
118-
code_line = caller.lineno
119-
if log_type == "info":
120-
logger.info(f" Line {code_line}]: {msg}")
121-
elif log_type == "warning":
122-
logger.warning(f" Line {code_line}]: {msg}")
123-
elif log_type == "error":
124-
logger.error(f" Line {code_line}]: {msg}")
125-
else:
126-
raise ValueError('unknown logtype')
127-
128-
# logging is nice to have but we don't want an error with the log to interfere with the code flow
129-
except Exception as e:
130-
print(e)
131-
13298
@staticmethod
13399
def write_info(msg: str, code_line: int = None):
134-
LoggerHelper.actual_write("info", LoggerHelper.log_instance, msg, code_line)
100+
if code_line is None:
101+
caller = getframeinfo(stack()[1][0])
102+
code_line = caller.lineno
103+
LoggerHelper.log_instance.info(f" Line {code_line}]: {msg}")
135104

136105
@staticmethod
137106
def write_warning(msg: str, code_line: int = None):
138-
LoggerHelper.actual_write("warning", LoggerHelper.log_instance, msg, code_line)
107+
if code_line is None:
108+
caller = getframeinfo(stack()[1][0])
109+
code_line = caller.lineno
110+
LoggerHelper.log_instance.warning(f" Line {code_line}]: {msg}")
139111

140112
@staticmethod
141113
def write_error(msg: str, code_line: int = None):
142-
LoggerHelper.actual_write("error", LoggerHelper.log_instance, msg, code_line)
114+
if code_line is None:
115+
caller = getframeinfo(stack()[1][0])
116+
code_line = caller.lineno
117+
LoggerHelper.log_instance.error(f" Line {code_line}]: {msg}")
143118

144119

145120
# =====================================================================================================================
146121

122+
147123
class FileInfo:
148124
def __init__(self, file_path: str):
149125
self.file_path = file_path
@@ -183,7 +159,7 @@ def __init__(self, resource_type: str, resource_name: str, tags):
183159

184160
# =====================================================================================================================
185161

186-
#modified
162+
# modified
187163
@ExceptionWrapper.wrap_class
188164
class OverrideTagsTemplatesCreator:
189165
def __init__(self, tags_dict: dict, terraform_version: str):
@@ -194,13 +170,9 @@ def __init__(self, tags_dict: dict, terraform_version: str):
194170
self._terraform_syntax = TerraformSyntaxVersion.get_terraform_syntax(terraform_version)
195171
self._map_key_value_separator = self._get_map_key_value_separator()
196172

197-
# if os.path.exists(self.torque_tags_file_path):
198-
# LoggerHelper.write_info(f"Reading torque tags from \"{self.torque_tags_file_path}\"")
199-
# self._read_and_save_tags_from_file()
200-
# else:
201-
# LoggerHelper.write_error(f"\"{self.torque_tags_file_path}\" does not exists")
202-
# LoggerHelper.write_error(f"Could not find torque tags file, exit the tagging process")
203-
# exit(1)
173+
if not self.torque_tags_dict:
174+
LoggerHelper.write_error(f"Didn't get tags dict, exiting the tagging process")
175+
return
204176

205177
LoggerHelper.write_info(f"Initiate default tags templates")
206178
self._init_torque_tags_flat_map()
@@ -383,8 +355,14 @@ def get_terraform_syntax(terraform_version: str):
383355
class Hcl2Parser:
384356
@staticmethod
385357
def get_tf_file_as_dict(tf_file_path: str) -> dict:
386-
with(open(tf_file_path, 'r')) as client_tf_file:
387-
return hcl2.load(client_tf_file)
358+
try:
359+
with(open(tf_file_path, 'r')) as client_tf_file:
360+
return hcl2.load(client_tf_file)
361+
except:
362+
# logging the file path that encountered the error
363+
LoggerHelper.write_error(f"Failed to parse tf file '{tf_file_path}'")
364+
# re-raising the exception so it will break the flow and its details are logged by the ExceptionWrapper
365+
raise
388366

389367
# full_resource_object contain many information in an unreadable structure
390368
# so we convert it to more less info with more readable structure
@@ -400,20 +378,14 @@ def get_terraform_resource_safely(full_resource_object: dict) -> TerraformResour
400378

401379
tags = full_resource_object[resource_type][resource_name].get('tags', None)
402380

403-
# When hcl2 parse a tf file it return all the tags blocks in a resource .
404-
# But a valid tf file (according to terraform) allow only one tags block in a resource.
405-
# So even if the hcl2 will return many tags blocks we will only be working with the first of them.
406-
# But because we run 'terraform init' and 'terraform plan' before we run this py file
407-
# we can be sure that if we have a tags block in the resource then we have only one
408-
# (because otherwise 'terraform plan' command would have return an error)
381+
# before version 3.0.0 of hcl2, "tags" was a list and we took the first element in it
382+
# this behavior was changed here: https://github.com/amplify-education/python-hcl2/blob/master/CHANGELOG.md#300---2021-07-14
409383
if tags:
410384
# we replace ' with " becuase hc2l has some bug:
411385
# for example it parse --> merge(local.common_tags, {"Name"="tomer"})
412386
# to --> merge(local.common_tags, {'Name'='tomer'}) and this is invalid syntax according to terraform
413-
if type(tags[0]) is str:
414-
tags = tags[0].replace("'", "\"").replace(",,", ",") # due to bug in hcl2 library
415-
else:
416-
tags = tags[0]
387+
if type(tags) is str:
388+
tags = tags.replace("'", "\"").replace(",,", ",") # due to bug in hcl2 library
417389

418390
return TerraformResource(resource_type=resource_type,
419391
resource_name=resource_name,
@@ -592,17 +564,16 @@ def create_override_file(self, untaggable_resources_types: List[str] = []):
592564
# M A I N
593565
# =====================================================================================================================
594566

567+
595568
# modified
596569
def _perform_terraform_init_plan(main_tf_dir_path: str, inputs_dict: dict):
597-
598570
inputs = []
571+
for input_key, input_value in inputs_dict.items():
572+
inputs.extend(['-var', f'{input_key}={input_value}'])
599573

600-
for inputkey, inputvalue in inputs_dict.items():
601-
inputs.extend(['-var', f'{inputkey}={inputvalue}'])
602-
603-
executable_cmd = f'{os.path.join(main_tf_dir_path, "terraform.exe")}'
604-
init_command = [executable_cmd, 'init', '-no-color']
605-
plan_command = [executable_cmd, 'plan', '-no-color', '-input=false']
574+
terraform_exe_path = f'{os.path.join(main_tf_dir_path, "terraform.exe")}'
575+
init_command = [terraform_exe_path, 'init', '-no-color']
576+
plan_command = [terraform_exe_path, 'plan', '-no-color', '-input=false']
606577
plan_command.extend(inputs)
607578

608579
init = subprocess.Popen(init_command, cwd=main_tf_dir_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -625,33 +596,33 @@ def _get_untaggable_resources_types_from_plan_output(text: str) -> List[str]:
625596
RegexHelper.UNSUPPORTED_TAGS_OR_LABELS_PATTERN_2])
626597
return untaggable_resources
627598

628-
#modified
629-
def start_tagging_terraform_resources(main_dir_path: str, logger, tags_dict: dict, inputs_dict: dict = dict(), terraform_version: str = ""):
599+
600+
# modified
601+
def start_tagging_terraform_resources(main_dir_path: str, logger, tags_dict: dict, inputs_dict: dict = None,
602+
terraform_version: str = ""):
630603
if not os.path.exists(main_dir_path):
631604
raise TerraformAutoTagsError(f"Path {main_dir_path} does not exist")
632605
tfs_folder_path = main_dir_path
633-
# log_path = Constants.get_override_log_path(main_dir_path)
634-
# torque_tags_file_path = Constants.get_torque_tags_path(main_dir_path)
635606
exclude_from_tagging_file_path = Constants.get_exclude_from_tagging_file_path(main_dir_path)
636607

637608
# modified
638609
LoggerHelper.init_logging(logger)
639610

640611
LoggerHelper.write_info(f"Trying to preform terraform init & plan in the directory '{tfs_folder_path}'"
641612
" in order to check for any validation errors in tf files")
642-
stdout, stderr, return_code = _perform_terraform_init_plan(tfs_folder_path, inputs_dict)
613+
stdout, stderr, return_code = _perform_terraform_init_plan(tfs_folder_path, inputs_dict) # modified
643614
if return_code != 0 or stderr:
644615
LoggerHelper.write_error("Exit before the override procedure began because the init/plan failed."
645616
f" (Return_code is {return_code})"
646617
f"\n\nErrors are:\n{stderr}\n")
647618
# Error Code 3 mark to the outside tf script (that run me) that there was an error but not because of
648619
# the override procedure (but because the client tf file has compile errors even before we started the
649620
# override procedure)
650-
raise TerraformAutoTagsError("Validation errors during Terraform Init/Plan when applying automated tags")
621+
exit(3)
651622
LoggerHelper.write_info(f"terraform init & plan passed successfully")
652623

653-
#modified
654-
tags_templates_creator = OverrideTagsTemplatesCreator(tags_dict, terraform_version=terraform_version)
624+
# modified
625+
tags_templates_creator = OverrideTagsTemplatesCreator(tags_dict, terraform_version)
655626

656627
all_tf_files = FilesHelper.get_all_files(tfs_folder_path, ".tf")
657628
all_tf_files_without_overrides = [file for file in all_tf_files if not file.file_name.endswith("_override.tf")]
@@ -672,7 +643,6 @@ def start_tagging_terraform_resources(main_dir_path: str, logger, tags_dict: dic
672643

673644
LoggerHelper.write_info(f"Trying to preform terraform init & plan in the directory '{tfs_folder_path}'"
674645
" in order to check for any untaggable resources in the override files")
675-
676646
# modified
677647
# Check (by analyzing the terraform plan output) to see if any of the override files
678648
# has a "tags/labels" that was assigned to untaggable resources
@@ -732,8 +702,8 @@ def _validate_terraform_version_arg(terraform_version_arg: str) -> bool:
732702
if not terraform_version_arg:
733703
return False
734704

735-
version_arr = terraform_version.split(".")
705+
version_arr = terraform_version_arg.split(".")
736706
if len(version_arr) != 3:
737707
return False
738708

739-
return version_arr[0].isdigit() and version_arr[1].isdigit()
709+
return version_arr[0].isdigit() and version_arr[1].isdigit()

package/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.2.0
1+
1.2.1

0 commit comments

Comments
 (0)