From 407713cf2314155329acc7e02725f109af6fba56 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Fri, 14 Mar 2025 19:30:01 -0400 Subject: [PATCH] a little cleanup --- .../gfl-resource-actions/config.py | 1 + .../gfl-resource-actions/logging_utils.py | 3 +- .../python-tools/gfl-resource-actions/main.py | 10 +- .../gfl-resource-actions/managers/base.py | 91 ++++++++++++++----- .../gfl-resource-actions/managers/ec2.py | 5 + .../gfl-resource-actions/managers/eks.py | 47 ++++++++++ .../gfl-resource-actions/managers/emr.py | 5 + .../gfl-resource-actions/managers/rds.py | 5 + 8 files changed, 139 insertions(+), 28 deletions(-) diff --git a/local-app/python-tools/gfl-resource-actions/config.py b/local-app/python-tools/gfl-resource-actions/config.py index a3640eba..c6d9defe 100644 --- a/local-app/python-tools/gfl-resource-actions/config.py +++ b/local-app/python-tools/gfl-resource-actions/config.py @@ -25,3 +25,4 @@ # Log file name LOG_FILE = "aws_resource_management.log" +ACTION_CSV_FILE = 'resource_actions.csv' diff --git a/local-app/python-tools/gfl-resource-actions/logging_utils.py b/local-app/python-tools/gfl-resource-actions/logging_utils.py index c9c51b94..8f22f3b0 100644 --- a/local-app/python-tools/gfl-resource-actions/logging_utils.py +++ b/local-app/python-tools/gfl-resource-actions/logging_utils.py @@ -6,6 +6,8 @@ import sys import csv import datetime +from config import ACTION_CSV_FILE + from pathlib import Path # Configure log formats @@ -14,7 +16,6 @@ # Configure CSV logging CSV_LOG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'logs') -ACTION_CSV_FILE = 'resource_actions.csv' CSV_FIELDS = ['timestamp', 'account_id', 'account_name', 'resource_type', 'resource_id', 'resource_name', 'action', 'region', 'status', 'details'] def setup_logging(name='resource_management', level=logging.INFO): diff --git a/local-app/python-tools/gfl-resource-actions/main.py b/local-app/python-tools/gfl-resource-actions/main.py index 9eae49a7..36d4c291 100644 --- a/local-app/python-tools/gfl-resource-actions/main.py +++ b/local-app/python-tools/gfl-resource-actions/main.py @@ -150,11 +150,11 @@ def process_account(account, credentials, regions, action, dry_run, exclude_reso logger.info(f"Account {account['id']} - Found {len(resources.get('eks_clusters', []))} EKS clusters") logger.info(f"Account {account['id']} - Found {len(resources.get('emr_clusters', []))} EMR clusters") - # Initialize resource managers - ec2_manager = EC2Manager(credentials, account['id']) - rds_manager = RDSManager(credentials, account['id']) - eks_manager = EKSManager(credentials, account['id']) - emr_manager = EMRManager(credentials, account['id']) + # Initialize resource managers with account name + ec2_manager = EC2Manager(credentials, account['id'], account['name']) + rds_manager = RDSManager(credentials, account['id'], account['name']) + eks_manager = EKSManager(credentials, account['id'], account['name']) + emr_manager = EMRManager(credentials, account['id'], account['name']) # Helper function to safely process manager results def safe_get_result(result, key): diff --git a/local-app/python-tools/gfl-resource-actions/managers/base.py b/local-app/python-tools/gfl-resource-actions/managers/base.py index 3c3a38a9..787eff14 100644 --- a/local-app/python-tools/gfl-resource-actions/managers/base.py +++ b/local-app/python-tools/gfl-resource-actions/managers/base.py @@ -1,43 +1,97 @@ """ -Base resource manager class with common functionality for AWS resources. +Base resource manager class that provides common functionality. """ import datetime +from aws_utils import create_boto3_client_for_account, get_partition_for_region import config -from aws_utils import create_boto3_client, get_partition_for_region from logging_utils import setup_logging logger = setup_logging() class ResourceManager: - """Base class for AWS resource managers.""" + """Base class for all resource managers.""" - def __init__(self, credentials, account_id): + def __init__(self, credentials, account_id, account_name=None): """ - Initialize resource manager. + Initialize resource manager with credentials. Args: credentials: AWS credentials dictionary - account_id: AWS account ID + account_id: AWS account ID + account_name: AWS account name (optional) """ self.credentials = credentials self.account_id = account_id - + self.account_name = account_name or account_id + self.resource_type = "generic" + def create_client(self, service, region): - """Create a boto3 client for the resource service.""" - return create_boto3_client(service, self.credentials, region) + """Create a boto3 client for the specified service and region.""" + if not self.credentials: + return None + + return boto3.client( + service, + region_name=region, + aws_access_key_id=self.credentials['AccessKeyId'], + aws_secret_access_key=self.credentials['SecretAccessKey'], + aws_session_token=self.credentials['SessionToken'] + ) + + def get_partition_for_region(self, region): + """Get AWS partition for the specified region.""" + return get_partition_for_region(region) def get_timestamp(self): - """Get ISO 8601 timestamp.""" - return datetime.datetime.now().isoformat() + """Get ISO 8601 timestamp for tagging.""" + return datetime.datetime.utcnow().isoformat() - def log_action(self, resource_id, region, action, details="", dry_run=False, existing_schedule=None): - """Log a resource action to CSV for audit trail.""" + def should_exclude(self, resource): + """Check if resource should be excluded based on tags.""" + tags = resource.get("tags", {}) + return config.EXCLUSION_TAG in tags + + def log_action(self, resource_id, region, action, resource_name=None, details=None, dry_run=False, existing_schedule=None): + """ + Log an action performed on a resource. + + Args: + resource_id: Resource identifier + region: AWS region + action: Action performed (stop, start, etc.) + resource_name: Optional resource name + details: Optional additional details + dry_run: Whether this was a dry run + existing_schedule: Existing schedule for the resource + """ + dry_run_prefix = "[DRY RUN] " if dry_run else "" + + # Build a detailed log message + resource_desc = f"{resource_id}" + if resource_name: + resource_desc = f"{resource_name} ({resource_id})" + + account_desc = f"account {self.account_id}" + if self.account_name and self.account_name != self.account_id: + account_desc = f"{self.account_name} ({self.account_id})" + + action_desc = f"{action}ed" if action[-1] != 'e' else f"{action}d" + + message = f"{dry_run_prefix}{self.resource_type.upper()} {resource_desc} {action_desc} in {account_desc} region {region}" + + if details: + message += f" - {details}" + + # Add schedule information if available + if existing_schedule: + message += f" (Schedule: {existing_schedule})" + log_action_to_csv( account_id=self.account_id, account_name=self.account_name, resource_type=self.resource_type, resource_id=resource_id, - resource_name=resource_id, # Using ID as name for simplicity + resource_name=resource_name or resource_id, # Use ID as name if not provided action=action, region=region, status="simulated" if dry_run else "success", @@ -45,11 +99,4 @@ def log_action(self, resource_id, region, action, details="", dry_run=False, exi dry_run=dry_run, existing_schedule=existing_schedule ) - - def should_exclude(self, resource): - """Check if resource should be excluded based on tags.""" - return config.EXCLUSION_TAG in resource.get("tags", {}) - - def get_partition_for_region(self, region): - """Get AWS partition for region.""" - return get_partition_for_region(region) + logger.info(message) diff --git a/local-app/python-tools/gfl-resource-actions/managers/ec2.py b/local-app/python-tools/gfl-resource-actions/managers/ec2.py index fc3ef2f9..f6272e10 100644 --- a/local-app/python-tools/gfl-resource-actions/managers/ec2.py +++ b/local-app/python-tools/gfl-resource-actions/managers/ec2.py @@ -10,6 +10,11 @@ class EC2Manager(base.ResourceManager): """Manager for EC2 instances.""" + def __init__(self, credentials, account_id, account_name=None): + """Initialize EC2 manager.""" + super().__init__(credentials, account_id, account_name) + self.resource_type = "ec2_instance" + def should_exclude(self, instance): """Check if EC2 instance should be excluded based on tags.""" tags = instance.get("tags", {}) diff --git a/local-app/python-tools/gfl-resource-actions/managers/eks.py b/local-app/python-tools/gfl-resource-actions/managers/eks.py index f0a2c643..7467a81c 100644 --- a/local-app/python-tools/gfl-resource-actions/managers/eks.py +++ b/local-app/python-tools/gfl-resource-actions/managers/eks.py @@ -20,6 +20,53 @@ class EKSManager(base.ResourceManager): """Manager for EKS clusters.""" + def __init__(self, credentials, account_id, account_name=None): + """Initialize EKS manager.""" + super().__init__(credentials, account_id, account_name) + self.resource_type = "eks_cluster" + + # Add stop method that delegates to scale_down + def stop(self, clusters, dry_run=False): + """ + Stop EKS clusters by scaling down their nodegroups. + + Args: + clusters: List of EKS cluster dictionaries + dry_run: If True, only simulate the action + + Returns: + dict: Summary of actions taken + """ + logger.info(f"Delegating EKS stop operation to scale_down for {len(clusters)} clusters") + self.scale_down(clusters, dry_run) + + # Return a result dictionary that matches the expected interface + return { + "success": len(clusters), + "errors": 0 + } + + # Add start method that delegates to scale_up + def start(self, clusters, dry_run=False): + """ + Start EKS clusters by scaling up their nodegroups. + + Args: + clusters: List of EKS cluster dictionaries + dry_run: If True, only simulate the action + + Returns: + dict: Summary of actions taken + """ + logger.info(f"Delegating EKS start operation to scale_up for {len(clusters)} clusters") + self.scale_up(clusters, dry_run) + + # Return a result dictionary that matches the expected interface + return { + "success": len(clusters), + "errors": 0 + } + def scale_down(self, clusters, dry_run=False): """Scale down EKS clusters by setting node counts to 0.""" logger.info(f"Evaluating {len(clusters)} EKS clusters for scale down action") diff --git a/local-app/python-tools/gfl-resource-actions/managers/emr.py b/local-app/python-tools/gfl-resource-actions/managers/emr.py index 07865373..9e58df24 100644 --- a/local-app/python-tools/gfl-resource-actions/managers/emr.py +++ b/local-app/python-tools/gfl-resource-actions/managers/emr.py @@ -10,6 +10,11 @@ class EMRManager(base.ResourceManager): """Manager for EMR clusters.""" + def __init__(self, credentials, account_id, account_name=None): + """Initialize EMR manager.""" + super().__init__(credentials, account_id, account_name) + self.resource_type = "emr_cluster" + def stop(self, clusters, dry_run=False): """ Stop EMR clusters gracefully. diff --git a/local-app/python-tools/gfl-resource-actions/managers/rds.py b/local-app/python-tools/gfl-resource-actions/managers/rds.py index 7db05a94..18631761 100644 --- a/local-app/python-tools/gfl-resource-actions/managers/rds.py +++ b/local-app/python-tools/gfl-resource-actions/managers/rds.py @@ -10,6 +10,11 @@ class RDSManager(base.ResourceManager): """Manager for RDS instances.""" + def __init__(self, credentials, account_id, account_name=None): + """Initialize RDS manager.""" + super().__init__(credentials, account_id, account_name) + self.resource_type = "rds_instance" + def stop(self, instances, dry_run=False): """Stop available RDS instances.""" logger.info(f"Evaluating {len(instances)} RDS instances for stop action")