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
2019import re
2120import enum
22- import sys
2321import traceback
2422from typing import List
2523import hcl2
3331### Added
3432from cloudshell .iac .terraform .models .exceptions import TerraformAutoTagsError
3533
34+
3635# =====================================================================================================================
3736
38- #commented out some contants and methods but isnt required
3937class 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
10691class 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+
147123class 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
188164class 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):
383355class 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
596569def _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 \n Errors 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 ()
0 commit comments