from pathlib import Path from typing import Optional, Any, Dict, List, Union from typing_extensions import Literal from phi.aws.api_client import AwsApiClient from phi.aws.resource.base import AwsResource from phi.aws.resource.cloudformation.stack import CloudFormationStack from phi.aws.resource.ec2.security_group import SecurityGroup from phi.aws.resource.rds.db_subnet_group import DbSubnetGroup from phi.aws.resource.secret.manager import SecretsManager from phi.cli.console import print_info from phi.utils.log import logger class DbInstance(AwsResource): """ The DBInstance can be an RDS DB instance, or it can be a DB instance in an Aurora DB cluster. Reference: - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/rds.html """ resource_type: Optional[str] = "DbInstance" service_name: str = "rds" # Name of the db instance. name: str # The name of the database engine to be used for this instance. engine: Union[ str, Literal[ "aurora", "aurora-mysql", "aurora-postgresql", "custom-oracle-ee", "custom-sqlserver-ee", "custom-sqlserver-se", "custom-sqlserver-web", "mariadb", "mysql", "oracle-ee", "oracle-ee-cdb", "oracle-se2", "oracle-se2-cdb", "postgres", "sqlserver-ee", "sqlserver-se", "sqlserver-ex", "sqlserver-web", ], ] # The version number of the database engine to use. engine_version: Optional[str] = None # Compute and memory capacity of the DB instance, for example db.m5.large. db_instance_class: Optional[str] = None # This is the name of the database to create when the DB instance is created. # Note: The meaning of this parameter differs according to the database engine you use. # Provide DB_NAME here or as DB_NAME in secrets_file db_name: Optional[str] = None # The identifier for this DB instance. This parameter is stored as a lowercase string. # If None, use the name as the db_instance_identifier # Constraints: # - Must contain from 1 to 63 letters, numbers, or hyphens. # - First character must be a letter. # - Can't end with a hyphen or contain two consecutive hyphens. db_instance_identifier: Optional[str] = None # The amount of storage in gibibytes (GiB) to allocate for the DB instance. allocated_storage: Optional[int] = None # The name of the DB parameter group to associate with this DB instance. db_parameter_group_name: Optional[str] = None # The port number on which the database accepts connections. port: Optional[int] = None # The name for the master user. # Provide MASTER_USERNAME here or as MASTER_USERNAME in secrets_file master_username: Optional[str] = None # The password for the master user. # The password can include any printable ASCII character except "/", """, or "@". # Provide MASTER_USER_PASSWORD here or as MASTER_USER_PASSWORD in secrets_file master_user_password: Optional[str] = None # Read secrets from a file in yaml format secrets_file: Optional[Path] = None # Read secret variables from AWS Secret aws_secret: Optional[SecretsManager] = None # The Availability Zone (AZ) where the database will be created. availability_zone: Optional[str] = None # A DB subnet group to associate with this DB instance. db_subnet_group_name: Optional[str] = None # If db_subnet_group_name is None, # Read the db_subnet_group_name from db_subnet_group db_subnet_group: Optional[DbSubnetGroup] = None # Specifies whether the DB instance is publicly accessible. # When the DB instance is publicly accessible, its Domain Name System (DNS) endpoint resolves to the private IP # from within the DB instance's virtual private cloud (VPC). It resolves to the public IP address from outside # the DB instance's VPC. Access to the DB instance is ultimately controlled by the security group it uses. # That public access is not permitted if the security group assigned to the DB instance doesn't permit it. # # When the DB instance isn't publicly accessible, it is an internal DB instance with a DNS name that resolves # to a private IP address. publicly_accessible: Optional[bool] = None # The identifier of the DB cluster that the instance will belong to. db_cluster_identifier: Optional[str] = None # Specifies the storage type to be associated with the DB instance. # Valid values: gp2 | gp3 | io1 | standard # If you specify io1 or gp3 , you must also include a value for the Iops parameter. # Default: io1 if the Iops parameter is specified, otherwise gp2 storage_type: Optional[str] = None iops: Optional[int] = None # A list of VPC security groups to associate with this DB instance. vpc_security_group_ids: Optional[List[str]] = None # If vpc_security_group_ids is None, # Read the security_group_id from vpc_stack vpc_stack: Optional[CloudFormationStack] = None # Add security_group_ids from db_security_groups db_security_groups: Optional[List[SecurityGroup]] = None backup_retention_period: Optional[int] = None character_set_name: Optional[str] = None preferred_backup_window: Optional[str] = None # The time range each week during which system maintenance can occur, in Universal Coordinated Time (UTC). preferred_maintenance_window: Optional[str] = None # A value that indicates whether the DB instance is a Multi-AZ deployment. # You can't set the AvailabilityZone parameter if the DB instance is a Multi-AZ deployment. multi_az: Optional[bool] = None auto_minor_version_upgrade: Optional[bool] = None license_model: Optional[str] = None option_group_name: Optional[str] = None nchar_character_set_name: Optional[str] = None tags: Optional[List[Dict[str, str]]] = None tde_credential_arn: Optional[str] = None tde_credential_password: Optional[str] = None storage_encrypted: Optional[bool] = None kms_key_id: Optional[str] = None domain: Optional[str] = None copy_tags_to_snapshot: Optional[bool] = None monitoring_interval: Optional[int] = None monitoring_role_arn: Optional[str] = None domain_iam_role_name: Optional[str] = None promotion_tier: Optional[int] = None timezone: Optional[str] = None enable_iam_database_authentication: Optional[bool] = None enable_performance_insights: Optional[bool] = None performance_insights_kms_key_id: Optional[str] = None performance_insights_retention_period: Optional[int] = None enable_cloudwatch_logs_exports: Optional[List[str]] = None processor_features: Optional[List[Dict[str, str]]] = None # A value that indicates whether the DB instance has deletion protection enabled. The database can't be deleted # when deletion protection is enabled. By default, deletion protection isn't enabled. deletion_protection: Optional[bool] = None # The upper limit in gibibytes (GiB) to which Amazon RDS can automatically scale the storage of the DB instance. max_allocated_storage: Optional[int] = None enable_customer_owned_ip: Optional[bool] = None custom_iam_instance_profile: Optional[str] = None backup_target: Optional[str] = None network_type: Optional[str] = None storage_throughput: Optional[int] = None ca_certificate_identifier: Optional[str] = None db_system_id: Optional[str] = None dedicated_log_volume: Optional[bool] = None # Specifies whether to manage the master user password with Amazon Web Services Secrets Manager. # Constraints: # Can’t manage the master user password with Amazon Web Services Secrets Manager if MasterUserPassword is specified. manage_master_user_password: Optional[bool] = None # The Amazon Web Services KMS key identifier to encrypt a secret that is automatically generated and # managed in Amazon Web Services Secrets Manager. master_user_secret_kms_key_id: Optional[str] = None # Parameters for delete function # Skip the creation of a final DB snapshot before the instance is deleted. # If skip_final_snapshot = True, no DB snapshot is created. # If skip_final_snapshot = None | False, a DB snapshot is created before the instance is deleted. # You must specify a FinalDBSnapshotIdentifier parameter # if skip_final_snapshot = None | False skip_final_snapshot: Optional[bool] = True # The DB cluster snapshot identifier of the new DB cluster snapshot created when SkipFinalSnapshot is disabled. final_db_snapshot_identifier: Optional[str] = None # Specifies whether to remove automated backups immediately after the DB cluster is deleted. # The default is to remove automated backups immediately after the DB cluster is deleted. delete_automated_backups: Optional[bool] = None # Parameters for update function apply_immediately: Optional[bool] = True allow_major_version_upgrade: Optional[bool] = None new_db_instance_identifier: Optional[str] = None db_port_number: Optional[int] = None cloudwatch_logs_export_configuration: Optional[Dict[str, Any]] = None use_default_processor_features: Optional[bool] = None certificate_rotation_restart: Optional[bool] = None replica_mode: Optional[str] = None aws_backup_recovery_point_arn: Optional[str] = None automation_mode: Optional[str] = None resume_full_automation_mode_minutes: Optional[int] = None rotate_master_user_password: Optional[bool] = None # Cache secret_data cached_secret_data: Optional[Dict[str, Any]] = None def get_db_instance_identifier(self): return self.db_instance_identifier or self.name def get_master_username(self, aws_client: Optional[AwsApiClient] = None) -> Optional[str]: master_username = self.master_username if master_username is None and self.secrets_file is not None: # read from secrets_file secret_data = self.get_secret_file_data() if secret_data is not None: master_username = secret_data.get("MASTER_USERNAME", master_username) if master_username is None and self.aws_secret is not None: # read from aws_secret logger.debug(f"Reading MASTER_USERNAME from secret: {self.aws_secret.name}") master_username = self.aws_secret.get_secret_value("MASTER_USERNAME", aws_client=aws_client) return master_username def get_master_user_password(self, aws_client: Optional[AwsApiClient] = None) -> Optional[str]: master_user_password = self.master_user_password if master_user_password is None and self.secrets_file is not None: # read from secrets_file secret_data = self.get_secret_file_data() if secret_data is not None: master_user_password = secret_data.get("MASTER_USER_PASSWORD", master_user_password) if master_user_password is None and self.aws_secret is not None: # read from aws_secret logger.debug(f"Reading MASTER_USER_PASSWORD from secret: {self.aws_secret.name}") master_user_password = self.aws_secret.get_secret_value("MASTER_USER_PASSWORD", aws_client=aws_client) return master_user_password def get_db_name(self, aws_client: Optional[AwsApiClient] = None) -> Optional[str]: db_name = self.db_name if db_name is None and self.secrets_file is not None: # read from secrets_file secret_data = self.get_secret_file_data() if secret_data is not None: db_name = secret_data.get("DB_NAME", db_name) if db_name is None: db_name = secret_data.get("DATABASE_NAME", db_name) if db_name is None and self.aws_secret is not None: # read from aws_secret logger.debug(f"Reading DB_NAME from secret: {self.aws_secret.name}") db_name = self.aws_secret.get_secret_value("DB_NAME", aws_client=aws_client) if db_name is None: logger.debug(f"Reading DATABASE_NAME from secret: {self.aws_secret.name}") db_name = self.aws_secret.get_secret_value("DATABASE_NAME", aws_client=aws_client) return db_name def get_database_name(self) -> Optional[str]: # Alias for get_db_name because db_instances use `db_name` and db_clusters use `database_name` return self.get_db_name() def _create(self, aws_client: AwsApiClient) -> bool: """Creates the DbInstance Args: aws_client: The AwsApiClient for the current cluster """ print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}") # create a dict of args which are not null, otherwise aws type validation fails not_null_args: Dict[str, Any] = {} # Step 1: Get the VpcSecurityGroupIds vpc_security_group_ids = self.vpc_security_group_ids if vpc_security_group_ids is None and self.vpc_stack is not None: vpc_stack_sg = self.vpc_stack.get_security_group(aws_client=aws_client) if vpc_stack_sg is not None: vpc_security_group_ids = [vpc_stack_sg] if self.db_security_groups is not None: sg_ids = [] for sg in self.db_security_groups: sg_id = sg.get_security_group_id(aws_client) if sg_id is not None: sg_ids.append(sg_id) if len(sg_ids) > 0: if vpc_security_group_ids is None: vpc_security_group_ids = [] vpc_security_group_ids.extend(sg_ids) if vpc_security_group_ids is not None: logger.debug(f"Using SecurityGroups: {vpc_security_group_ids}") not_null_args["VpcSecurityGroupIds"] = vpc_security_group_ids # Step 2: Get the DbSubnetGroupName db_subnet_group_name = self.db_subnet_group_name if db_subnet_group_name is None and self.db_subnet_group is not None: db_subnet_group_name = self.db_subnet_group.name logger.debug(f"Using DbSubnetGroup: {db_subnet_group_name}") if db_subnet_group_name is not None: not_null_args["DBSubnetGroupName"] = db_subnet_group_name db_name = self.get_db_name() if db_name: not_null_args["DBName"] = db_name master_username = self.get_master_username() if master_username: not_null_args["MasterUsername"] = master_username master_user_password = self.get_master_user_password() if master_user_password: not_null_args["MasterUserPassword"] = master_user_password if self.allocated_storage: not_null_args["AllocatedStorage"] = self.allocated_storage if self.db_instance_class: not_null_args["DBInstanceClass"] = self.db_instance_class if self.availability_zone is not None: not_null_args["AvailabilityZone"] = self.availability_zone if self.preferred_maintenance_window: not_null_args["PreferredMaintenanceWindow"] = self.preferred_maintenance_window if self.db_parameter_group_name: not_null_args["DBParameterGroupName"] = self.db_parameter_group_name if self.backup_retention_period: not_null_args["BackupRetentionPeriod"] = self.backup_retention_period if self.preferred_backup_window: not_null_args["PreferredBackupWindow"] = self.preferred_backup_window if self.port: not_null_args["Port"] = self.port if self.multi_az: not_null_args["MultiAZ"] = self.multi_az if self.engine_version: not_null_args["EngineVersion"] = self.engine_version if self.auto_minor_version_upgrade: not_null_args["AutoMinorVersionUpgrade"] = self.auto_minor_version_upgrade if self.license_model: not_null_args["LicenseModel"] = self.license_model if self.iops: not_null_args["Iops"] = self.iops if self.option_group_name: not_null_args["OptionGroupName"] = self.option_group_name if self.character_set_name: not_null_args["CharacterSetName"] = self.character_set_name if self.nchar_character_set_name: not_null_args["NcharCharacterSetName"] = self.nchar_character_set_name if self.publicly_accessible: not_null_args["PubliclyAccessible"] = self.publicly_accessible if self.tags: not_null_args["Tags"] = self.tags if self.db_cluster_identifier: not_null_args["DBClusterIdentifier"] = self.db_cluster_identifier if self.storage_type: not_null_args["StorageType"] = self.storage_type if self.tde_credential_arn: not_null_args["TdeCredentialArn"] = self.tde_credential_arn if self.tde_credential_password: not_null_args["TdeCredentialPassword"] = self.tde_credential_password if self.storage_encrypted: not_null_args["StorageEncrypted"] = self.storage_encrypted if self.kms_key_id: not_null_args["KmsKeyId"] = self.kms_key_id if self.domain: not_null_args["Domain"] = self.domain if self.copy_tags_to_snapshot: not_null_args["CopyTagsToSnapshot"] = self.copy_tags_to_snapshot if self.monitoring_interval: not_null_args["MonitoringInterval"] = self.monitoring_interval if self.monitoring_role_arn: not_null_args["MonitoringRoleArn"] = self.monitoring_role_arn if self.domain_iam_role_name: not_null_args["DomainIAMRoleName"] = self.domain_iam_role_name if self.promotion_tier: not_null_args["PromotionTier"] = self.promotion_tier if self.timezone: not_null_args["Timezone"] = self.timezone if self.enable_iam_database_authentication: not_null_args["EnableIAMDatabaseAuthentication"] = self.enable_iam_database_authentication if self.enable_performance_insights: not_null_args["EnablePerformanceInsights"] = self.enable_performance_insights if self.performance_insights_kms_key_id: not_null_args["PerformanceInsightsKMSKeyId"] = self.performance_insights_kms_key_id if self.performance_insights_retention_period: not_null_args["PerformanceInsightsRetentionPeriod"] = self.performance_insights_retention_period if self.enable_cloudwatch_logs_exports: not_null_args["EnableCloudwatchLogsExports"] = self.enable_cloudwatch_logs_exports if self.processor_features: not_null_args["ProcessorFeatures"] = self.processor_features if self.deletion_protection: not_null_args["DeletionProtection"] = self.deletion_protection if self.max_allocated_storage: not_null_args["MaxAllocatedStorage"] = self.max_allocated_storage if self.enable_customer_owned_ip: not_null_args["EnableCustomerOwnedIp"] = self.enable_customer_owned_ip if self.custom_iam_instance_profile: not_null_args["CustomIamInstanceProfile"] = self.custom_iam_instance_profile if self.backup_target: not_null_args["BackupTarget"] = self.backup_target if self.network_type: not_null_args["NetworkType"] = self.network_type if self.storage_throughput: not_null_args["StorageThroughput"] = self.storage_throughput if self.ca_certificate_identifier: not_null_args["CACertificateIdentifier"] = self.ca_certificate_identifier if self.db_system_id: not_null_args["DBSystemId"] = self.db_system_id if self.dedicated_log_volume: not_null_args["DedicatedLogVolume"] = self.dedicated_log_volume if self.manage_master_user_password: not_null_args["ManageMasterUserPassword"] = self.manage_master_user_password if self.master_user_secret_kms_key_id: not_null_args["MasterUserSecretKmsKeyId"] = self.master_user_secret_kms_key_id # Step 3: Create DBInstance service_client = self.get_service_client(aws_client) try: create_response = service_client.create_db_instance( DBInstanceIdentifier=self.get_db_instance_identifier(), Engine=self.engine, **not_null_args, ) logger.debug(f"Response: {create_response}") resource_dict = create_response.get("DBInstance", {}) # Validate resource creation if resource_dict is not None: logger.debug(f"DBInstance created: {self.get_db_instance_identifier()}") self.active_resource = resource_dict return True except Exception as e: logger.error(f"{self.get_resource_type()} could not be created.") logger.error(e) return False def post_create(self, aws_client: AwsApiClient) -> bool: # Wait for DbInstance to be created if self.wait_for_create: try: print_info(f"Waiting for {self.get_resource_type()} to be active.") waiter = self.get_service_client(aws_client).get_waiter("db_instance_available") waiter.wait( DBInstanceIdentifier=self.get_db_instance_identifier(), WaiterConfig={ "Delay": self.waiter_delay, "MaxAttempts": self.waiter_max_attempts, }, ) except Exception as e: logger.error("Waiter failed.") logger.error(e) return True def _read(self, aws_client: AwsApiClient) -> Optional[Any]: """Returns the DbInstance Args: aws_client: The AwsApiClient for the current cluster """ logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}") from botocore.exceptions import ClientError service_client = self.get_service_client(aws_client) try: resource_identifier = self.get_db_instance_identifier() describe_response = service_client.describe_db_instances(DBInstanceIdentifier=resource_identifier) # logger.debug(f"DbInstance: {describe_response}") resources_list = describe_response.get("DBInstances", None) if resources_list is not None and isinstance(resources_list, list): for _resource in resources_list: _identifier = _resource.get("DBInstanceIdentifier", None) if _identifier == resource_identifier: self.active_resource = _resource break except ClientError as ce: logger.debug(f"ClientError: {ce}") except Exception as e: logger.error(f"Error reading {self.get_resource_type()}.") logger.error(e) return self.active_resource def _delete(self, aws_client: AwsApiClient) -> bool: """Deletes the DbInstance Args: aws_client: The AwsApiClient for the current cluster """ print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}") service_client = self.get_service_client(aws_client) self.active_resource = None # create a dict of args which are not null, otherwise aws type validation fails not_null_args: Dict[str, Any] = {} if self.final_db_snapshot_identifier: not_null_args["FinalDBSnapshotIdentifier"] = self.final_db_snapshot_identifier if self.delete_automated_backups: not_null_args["DeleteAutomatedBackups"] = self.delete_automated_backups try: db_instance_identifier = self.get_db_instance_identifier() delete_response = service_client.delete_db_instance( DBInstanceIdentifier=db_instance_identifier, SkipFinalSnapshot=self.skip_final_snapshot, **not_null_args, ) logger.debug(f"Response: {delete_response}") resource_dict = delete_response.get("DBInstance", {}) # Validate resource creation if resource_dict is not None: logger.debug(f"DBInstance deleted: {self.get_db_instance_identifier()}") self.active_resource = resource_dict return True except Exception as e: logger.error(f"{self.get_resource_type()} could not be deleted.") logger.error("Please try again or delete resources manually.") logger.error(e) return False def post_delete(self, aws_client: AwsApiClient) -> bool: # Wait for DbInstance to be deleted if self.wait_for_delete: try: print_info(f"Waiting for {self.get_resource_type()} to be deleted.") waiter = self.get_service_client(aws_client).get_waiter("db_instance_deleted") waiter.wait( DBInstanceIdentifier=self.get_db_instance_identifier(), WaiterConfig={ "Delay": self.waiter_delay, "MaxAttempts": self.waiter_max_attempts, }, ) except Exception as e: logger.error("Waiter failed.") logger.error(e) return True def _update(self, aws_client: AwsApiClient) -> bool: """Updates the DbInstance""" print_info(f"Updating {self.get_resource_type()}: {self.get_resource_name()}") # Step 1: Get existing DBInstance db_instance = self.read(aws_client) # create a dict of args which are not null, otherwise aws type validation fails not_null_args: Dict[str, Any] = { "ApplyImmediately": self.apply_immediately, } vpc_security_group_ids = self.vpc_security_group_ids if vpc_security_group_ids is None and self.vpc_stack is not None: vpc_stack_sg = self.vpc_stack.get_security_group(aws_client=aws_client) if vpc_stack_sg is not None: vpc_security_group_ids = [vpc_stack_sg] if self.db_security_groups is not None: sg_ids = [] for sg in self.db_security_groups: sg_id = sg.get_security_group_id(aws_client) if sg_id is not None: sg_ids.append(sg_id) if len(sg_ids) > 0: if vpc_security_group_ids is None: vpc_security_group_ids = [] vpc_security_group_ids.extend(sg_ids) # Check if vpc_security_group_ids has changed existing_vpc_security_group = db_instance.get("VpcSecurityGroups", []) existing_vpc_security_group_ids = [] for existing_sg in existing_vpc_security_group: existing_vpc_security_group_ids.append(existing_sg.get("VpcSecurityGroupId", None)) if vpc_security_group_ids is not None and vpc_security_group_ids != existing_vpc_security_group_ids: logger.info(f"Updating SecurityGroups: {vpc_security_group_ids}") not_null_args["VpcSecurityGroupIds"] = vpc_security_group_ids db_subnet_group_name = self.db_subnet_group_name if db_subnet_group_name is None and self.db_subnet_group is not None: db_subnet_group_name = self.db_subnet_group.name # Check if db_subnet_group_name has changed existing_db_subnet_group_name = db_instance.get("DBSubnetGroup", {}).get("DBSubnetGroupName", None) if db_subnet_group_name is not None and db_subnet_group_name != existing_db_subnet_group_name: logger.info(f"Updating DbSubnetGroup: {db_subnet_group_name}") not_null_args["DBSubnetGroupName"] = db_subnet_group_name master_user_password = self.get_master_user_password() if master_user_password: not_null_args["MasterUserPassword"] = master_user_password if self.allocated_storage: not_null_args["AllocatedStorage"] = self.allocated_storage if self.db_instance_class: not_null_args["DBInstanceClass"] = self.db_instance_class if self.db_parameter_group_name: not_null_args["DBParameterGroupName"] = self.db_parameter_group_name if self.backup_retention_period: not_null_args["BackupRetentionPeriod"] = self.backup_retention_period if self.preferred_backup_window: not_null_args["PreferredBackupWindow"] = self.preferred_backup_window if self.preferred_maintenance_window: not_null_args["PreferredMaintenanceWindow"] = self.preferred_maintenance_window if self.multi_az: not_null_args["MultiAZ"] = self.multi_az if self.engine_version: not_null_args["EngineVersion"] = self.engine_version if self.allow_major_version_upgrade: not_null_args["AllowMajorVersionUpgrade"] = self.allow_major_version_upgrade if self.auto_minor_version_upgrade: not_null_args["AutoMinorVersionUpgrade"] = self.auto_minor_version_upgrade if self.license_model: not_null_args["LicenseModel"] = self.license_model if self.iops: not_null_args["Iops"] = self.iops if self.option_group_name: not_null_args["OptionGroupName"] = self.option_group_name if self.new_db_instance_identifier: not_null_args["NewDBInstanceIdentifier"] = self.new_db_instance_identifier if self.storage_type: not_null_args["StorageType"] = self.storage_type if self.tde_credential_arn: not_null_args["TdeCredentialArn"] = self.tde_credential_arn if self.tde_credential_password: not_null_args["TdeCredentialPassword"] = self.tde_credential_password if self.ca_certificate_identifier: not_null_args["CACertificateIdentifier"] = self.ca_certificate_identifier if self.domain: not_null_args["Domain"] = self.domain if self.copy_tags_to_snapshot: not_null_args["CopyTagsToSnapshot"] = self.copy_tags_to_snapshot if self.monitoring_interval: not_null_args["MonitoringInterval"] = self.monitoring_interval if self.db_port_number: not_null_args["DBPortNumber"] = self.db_port_number if self.publicly_accessible: not_null_args["PubliclyAccessible"] = self.publicly_accessible if self.monitoring_role_arn: not_null_args["MonitoringRoleArn"] = self.monitoring_role_arn if self.domain_iam_role_name: not_null_args["DomainIAMRoleName"] = self.domain_iam_role_name if self.promotion_tier: not_null_args["PromotionTier"] = self.promotion_tier if self.enable_iam_database_authentication: not_null_args["EnableIAMDatabaseAuthentication"] = self.enable_iam_database_authentication if self.enable_performance_insights: not_null_args["EnablePerformanceInsights"] = self.enable_performance_insights if self.performance_insights_kms_key_id: not_null_args["PerformanceInsightsKMSKeyId"] = self.performance_insights_kms_key_id if self.performance_insights_retention_period: not_null_args["PerformanceInsightsRetentionPeriod"] = self.performance_insights_retention_period if self.cloudwatch_logs_export_configuration: not_null_args["CloudwatchLogsExportConfiguration"] = self.cloudwatch_logs_export_configuration if self.processor_features: not_null_args["ProcessorFeatures"] = self.processor_features if self.use_default_processor_features: not_null_args["UseDefaultProcessorFeatures"] = self.use_default_processor_features if self.deletion_protection: not_null_args["DeletionProtection"] = self.deletion_protection if self.max_allocated_storage: not_null_args["MaxAllocatedStorage"] = self.max_allocated_storage if self.certificate_rotation_restart: not_null_args["CertificateRotationRestart"] = self.certificate_rotation_restart if self.replica_mode: not_null_args["ReplicaMode"] = self.replica_mode if self.enable_customer_owned_ip: not_null_args["EnableCustomerOwnedIp"] = self.enable_customer_owned_ip if self.aws_backup_recovery_point_arn: not_null_args["AwsBackupRecoveryPointArn"] = self.aws_backup_recovery_point_arn if self.automation_mode: not_null_args["AutomationMode"] = self.automation_mode if self.resume_full_automation_mode_minutes: not_null_args["ResumeFullAutomationModeMinutes"] = self.resume_full_automation_mode_minutes if self.network_type: not_null_args["NetworkType"] = self.network_type if self.storage_throughput: not_null_args["StorageThroughput"] = self.storage_throughput if self.manage_master_user_password: not_null_args["ManageMasterUserPassword"] = self.manage_master_user_password if self.rotate_master_user_password: not_null_args["RotateMasterUserPassword"] = self.rotate_master_user_password if self.master_user_secret_kms_key_id: not_null_args["MasterUserSecretKmsKeyId"] = self.master_user_secret_kms_key_id # Step 2: Update DBInstance service_client = self.get_service_client(aws_client) try: update_response = service_client.modify_db_instance( DBInstanceIdentifier=self.get_db_instance_identifier(), **not_null_args, ) logger.debug(f"Response: {update_response}") resource_dict = update_response.get("DBInstance", {}) # Validate resource update if resource_dict is not None: print_info(f"DBInstance updated: {self.get_resource_name()}") self.active_resource = update_response return True except Exception as e: logger.error(f"{self.get_resource_type()} could not be created.") logger.error(e) return False def get_db_endpoint(self, aws_client: Optional[AwsApiClient] = None) -> Optional[str]: """Returns the DbInstance endpoint Returns: The DbInstance endpoint """ logger.debug(f"Getting endpoint for {self.get_resource_name()}") _db_endpoint: Optional[str] = None if self.active_resource: _db_endpoint = self.active_resource.get("Endpoint", {}).get("Address", None) if _db_endpoint is None: client: AwsApiClient = aws_client or self.get_aws_client() resource = self._read(aws_client=client) if resource is not None: _db_endpoint = resource.get("Endpoint", {}).get("Address", None) if _db_endpoint is None: resource = self.read_resource_from_file() if resource is not None: _db_endpoint = resource.get("Endpoint", {}).get("Address", None) logger.debug(f"DBInstance Endpoint: {_db_endpoint}") return _db_endpoint def get_db_port(self, aws_client: Optional[AwsApiClient] = None) -> Optional[str]: """Returns the DbInstance port Returns: The DbInstance port """ logger.debug(f"Getting port for {self.get_resource_name()}") _db_port: Optional[str] = None if self.active_resource: _db_port = self.active_resource.get("Endpoint", {}).get("Port", None) if _db_port is None: client: AwsApiClient = aws_client or self.get_aws_client() resource = self._read(aws_client=client) if resource is not None: _db_port = resource.get("Endpoint", {}).get("Port", None) if _db_port is None: resource = self.read_resource_from_file() if resource is not None: _db_port = resource.get("Endpoint", {}).get("Port", None) logger.debug(f"DBInstance Port: {_db_port}") return _db_port