Skip to content

Commit

Permalink
Simplify cleaning argument. Clean all proxy / retail / monitoring all…
Browse files Browse the repository at this point in the history
… together or keep them all. (#49)
  • Loading branch information
maximenoel8 authored Feb 13, 2025
1 parent 9bfa2a2 commit 4959718
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 40 deletions.
27 changes: 21 additions & 6 deletions terracumber-cli
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,26 @@ def parse_args():
parser.add_argument('--bastion_ssh_key', help="""Bastion key to be use""", dest='bastion_ssh_key', default=None)
parser.add_argument('--bastion_user', help="""Bastion user to be use""", dest='bastion_user', default='ec2-user')
parser.add_argument('--bastion_hostname', help="""Bastion hostname to use""", dest='bastion_hostname', default=None)
parser.add_argument('--use-tf-resource-cleaner', help='Activate or deactivate the minion cleaner',
action='store_true', default=False)
parser.add_argument('--tf-resources-to-keep', type=str, nargs='*', default=[], help='Space-separated list of minions resources to keep')
parser.add_argument('--tf-resources-to-delete', type=str, nargs='*', choices=['proxy', 'monitoring-server', 'retail'], default=[],
help='List of default modules to force deletion')
parser.add_argument(
'--use-tf-resource-cleaner',
help='Activate or deactivate the Terraform resource cleaner',
action='store_true',
default=False
)
parser.add_argument(
'--tf-resources-to-keep',
type=str,
nargs='*',
default=[],
help='Space-separated list of specific Terraform resources to keep (e.g., minions, clients)'
)
parser.add_argument(
'--tf-resources-delete-all',
help='If set, delete all resources except the default exclusions (e.g., minion, client, terminal, buildhost, proxy, dhcp_dns, monitoring_server). If not set, only client-related resources will be removed.',
action='store_true',
default=False
)


args = parser.parse_args()
if args.runstep:
Expand Down Expand Up @@ -240,7 +255,7 @@ def run_terraform(args, tf_vars):
terraform.taint(args.taint)
if args.destroy:
terraform.destroy()
result = terraform.apply(args.parallelism, args.use_tf_resource_cleaner, args.tf_resources_to_keep, args.tf_resources_to_delete)
result = terraform.apply(args.parallelism, args.use_tf_resource_cleaner, args.tf_resources_to_keep, args.tf_resources_delete_all)
if result == 0:
return True
return False
Expand Down
4 changes: 2 additions & 2 deletions terracumber/terraformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def taint(self, what):
print(resource)
self.__run_command([self.terraform_bin, "taint", "%s" % resource])

def apply(self, parallelism=10, use_tf_resource_cleaner=False, tf_resources_to_keep=[], tf_resources_to_delete=[]):
def apply(self, parallelism=10, use_tf_resource_cleaner=False, tf_resources_to_keep=[], delete_all=False):
"""Run terraform apply after removing unselected resources from the main.tf.
parallelism - Define the number of parallel resource operations. Defaults to 10 as specified by terraform.
Expand All @@ -122,7 +122,7 @@ def apply(self, parallelism=10, use_tf_resource_cleaner=False, tf_resources_to_k
"""
self.prepare_environment() # Ensure environment is prepared
if use_tf_resource_cleaner:
remove_unselected_tf_resources(f"{self.terraform_path}/main.tf", tf_resources_to_keep, tf_resources_to_delete)
remove_unselected_tf_resources(f"{self.terraform_path}/main.tf", tf_resources_to_keep, delete_all)

command_arguments = [self.terraform_bin, "apply", "-auto-approve", f"-parallelism={parallelism}"]
for file in self.tfvars_files:
Expand Down
50 changes: 23 additions & 27 deletions terracumber/tf_module_cleaner.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,41 @@
logger = logging.getLogger(__name__)

"""
Extracts the default modules that should not be removed.
Extracts the default modules that should not be removed based on the `delete_all` flag.
:param maintf_content: Content of the main.tf file.
:param tf_resources_to_delete: List that can include 'proxy', 'monitoring', and 'retail' that will force to delete those modules.
:return: List of default module names to keep.
:param delete_all: Boolean indicating whether to delete all resources (True) or just clients (False).
:return: List of module names to keep.
"""
def get_default_modules(maintf_content, tf_resources_to_delete):
def get_default_modules(maintf_content, delete_all):
module_names = re.findall(r'module\s+"([^"]+)"', maintf_content)
exclusions = ['minion', 'client']

if tf_resources_to_delete:
if 'retail' in tf_resources_to_delete:
exclusions.extend(['terminal', 'buildhost', 'proxy', 'dhcp_dns'])
if 'proxy' in tf_resources_to_delete:
exclusions.extend(['proxy', 'dhcp_dns'])
if 'monitoring-server' in tf_resources_to_delete:
exclusions.append('monitoring_server')
if delete_all:
exclusions.extend(['terminal', 'buildhost', 'proxy', 'dhcp_dns', 'monitoring_server'])

filtered_module_names = [name for name in module_names if all(exclusion not in name for exclusion in exclusions)]

logger.info(f"Default modules are {filtered_module_names}")
logger.info(f"Default modules to keep: {filtered_module_names}")
return filtered_module_names

"""
Check for any resource in the line with exact match within '.'
Check for any resource in the line with exact match within '.'.
line - A configuration line from the controller
tf_resources_to_keep - List of resources to keep
:param line: A configuration line from the controller.
:param tf_resources_to_keep: List of resources to keep.
:return: Boolean indicating if the line contains a resource to keep.
"""
def contains_resource_name(line, tf_resources_to_keep):
return any(re.search(rf'\.{re.escape(resource)}\.', line) for resource in tf_resources_to_keep)

"""
Filters configuration lines in the controller module to retain only the configurations
for the resouces that should be kept. Also ensures that the output module is not deleted
(identified by having 'configuration' in the title). Removes workaround lines for clarity.
for the resources that should be kept. Also ensures that the output module is not deleted.
maintf_content - Content of the main.tf file
tf_resources_to_keep - List of resources to keep
:param maintf_content: Content of the main.tf file.
:param tf_resources_to_keep: List of resources to keep.
:return: Filtered main.tf content.
"""
def filter_module_references(maintf_content, tf_resources_to_keep):
lines = maintf_content.split('\n')
Expand All @@ -61,27 +57,27 @@ def filter_module_references(maintf_content, tf_resources_to_keep):
return '\n'.join(filtered_lines)

"""
Removes modules and controller references from resources not in the resources to keep list.
Removes modules and controller references for resources not in the resources to keep list.
Removes comments outside modules from main.tf.
maintf_file - Path to the main.tf file
tf_resources_to_keep - List of resources to keep
tf_resources_to_delete - List of resources to remove ( can only be proxy, monitoring-server and retail)
:param maintf_file: Path to the main.tf file.
:param tf_resources_to_keep - List of resources to keep
:param delete_all: Boolean indicating whether to delete all resources (True) or just clients (False).
"""
def remove_unselected_tf_resources(maintf_file, tf_resources_to_keep, tf_resources_to_delete):
def remove_unselected_tf_resources(maintf_file, tf_resources_to_keep, delete_all):
with open(maintf_file, 'r') as file:
raw_data = file.readlines()

filtered_lines = [line for line in raw_data if not line.lstrip().startswith("//")]
data = ''.join(filtered_lines)
modules = data.split("module ")
tf_resources_to_keep.extend(get_default_modules(data, tf_resources_to_delete))
tf_resources_to_keep.extend(get_default_modules(data, delete_all))
logger.info(f"Resources to keep {tf_resources_to_keep}.")

for module in modules[1:]:
module_name = module.split('"')[1]
if module_name not in tf_resources_to_keep :
logger.info(f"Removing minion {module_name} from main.tf")
if module_name not in tf_resources_to_keep:
logger.info(f"Removing module {module_name} from main.tf")
data = data.replace("module " + module, '')
elif module_name == 'controller':
data = data.replace(module, filter_module_references(module, tf_resources_to_keep))
Expand Down
20 changes: 15 additions & 5 deletions test/test_tf_module_cleaner.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ class TestTerraformFunctions(unittest.TestCase):
@patch('terracumber.tf_module_cleaner.logger.info')
def test_get_default_modules(self, mock_logger, mock_findall):
mock_findall.return_value = ['module.server', 'module.proxy', 'module.terminal']
tf_resources_to_delete = ['retail', 'proxy']

result = tf_module_cleaner.get_default_modules('content', tf_resources_to_delete)
# Test when delete_all=True (all resources except exclusions are kept)
result = tf_module_cleaner.get_default_modules('content', delete_all=True)
expected = ['module.server']
self.assertEqual(result, expected)

# Test when delete_all=False (keeps only client-related resources)
result = tf_module_cleaner.get_default_modules('content', delete_all=False)
expected = ['module.server', 'module.proxy', 'module.terminal']
self.assertEqual(result, expected)

def test_contains_resource_name(self):
Expand Down Expand Up @@ -76,14 +80,20 @@ def test_filter_module_references(self):
def test_remove_unselected_tf_resources(self, mock_logger, mock_filter_module_references, mock_get_default_modules, mock_open):
mock_get_default_modules.return_value = ['server']
mock_filter_module_references.return_value = 'module "rocky8-minion" { }'

tf_resources_to_keep = ['rocky8-minion']
tf_resources_to_delete = ['proxy']

tf_module_cleaner.remove_unselected_tf_resources('mockfile', tf_resources_to_keep, tf_resources_to_delete)
# Test when delete_all=True
tf_module_cleaner.remove_unselected_tf_resources('mockfile', tf_resources_to_keep, delete_all=False)

handle = mock_open()
handle.write.assert_called_once_with('module "server" { } ')

# Test when delete_all=False
mock_get_default_modules.return_value = ['server', 'proxy']
tf_module_cleaner.remove_unselected_tf_resources('mockfile', tf_resources_to_keep, delete_all=False)

handle = mock_open()
handle.write.assert_called_with('module "server" { } module "proxy" { } ')

if __name__ == '__main__':
unittest.main()

0 comments on commit 4959718

Please sign in to comment.