in source/idea/idea-administrator/src/ideaadministrator/app/patch_helper.py [0:0]
def patch_app(self):
self.context.info('searching for applicable ec2 instances ...')
describe_instances_result = self.context.aws().ec2().describe_instances(
Filters=[
{
'Name': 'instance-state-name',
'Values': ['pending', 'stopped', 'running']
},
{
'Name': f'tag:{constants.IDEA_TAG_ENVIRONMENT_NAME}',
'Values': [self.cluster_name]
},
{
'Name': f'tag:{constants.IDEA_TAG_MODULE_ID}',
'Values': [self.module_id]
},
{
'Name': f'tag:{constants.IDEA_TAG_NODE_TYPE}',
'Values': ['app']
}
]
)
instances_to_patch = []
instances_cannot_be_patched = []
reservations = Utils.get_value_as_list('Reservations', describe_instances_result, [])
for reservation in reservations:
instances = Utils.get_value_as_list('Instances', reservation)
for instance in instances:
ec2_instance = EC2Instance(instance)
if ec2_instance.state == 'running':
if Utils.is_empty(self.instance_selector) or self.instance_selector == 'all':
instances_to_patch.append(ec2_instance)
else:
if self.instance_selector == 'any':
instances_to_patch.append(ec2_instance)
break
else:
instances_cannot_be_patched = ec2_instance
if len(instances_cannot_be_patched) > 0:
self.context.warning('Below instances cannot be patched as the instances are not running: ')
self.print_ec2_instance_table(instances_cannot_be_patched)
if len(instances_to_patch) == 0:
self.context.warning('No instances found to be patched. Abort.')
return
self.print_ec2_instance_table(instances_to_patch)
if not self.force:
confirm = self.context.prompt(f'Are you sure you want to patch the above running ec2 instances for module: {self.module_name}?')
if not confirm:
self.context.info('Patch aborted!')
return
with self.context.spinner('patching ec2 instances via AWS Systems Manager (Run Command) ... '):
if Utils.is_empty(self.patch_command):
package_uri = self.try_get_s3_package_uri()
patch_command = self.get_patch_run_command(package_uri)
else:
patch_command = self.patch_command
print(f'patch command: {patch_command}')
instance_ids = []
for ec2_instance in instances_to_patch:
instance_ids.append(ec2_instance.instance_id)
send_command_result = self.context.aws().ssm().send_command(
InstanceIds=instance_ids,
DocumentName='AWS-RunShellScript',
Parameters={
'commands': [
f'sudo echo "# $(date) executing patch ..." >> {PATCH_LOG}',
patch_command,
f'sudo tail -10 {PATCH_LOG}'
]
}
)
command_id = send_command_result['Command']['CommandId']
while True:
list_command_invocations_result = self.context.aws().ssm().list_command_invocations(
CommandId=command_id,
Details=False
)
command_invocations = list_command_invocations_result['CommandInvocations']
completed_count = 0
failed_count = 0
for command_invocation in command_invocations:
status = command_invocation['Status']
if status in ('Success', 'TimedOut', 'Cancelled', 'Failed'):
completed_count += 1
if status in ('TimedOut', 'Cancelled', 'Failed'):
failed_count += 1
if len(command_invocations) > 0:
self.context.info(f'Patching completed on {completed_count} out of {len(command_invocations)} instances')
if completed_count == len(command_invocations) and len(command_invocations) > 0:
break
time.sleep(10)
list_command_invocations_result = self.context.aws().ssm().list_command_invocations(
CommandId=command_id,
Details=True
)
command_invocations = list_command_invocations_result['CommandInvocations']
instance_id_to_command_invocations = {}
for command_invocation in command_invocations:
instance_id = command_invocation['InstanceId']
instance_id_to_command_invocations[instance_id] = command_invocation
self.context.info(f'Patch execution status for SSM Command Id: {command_id}')
table = PrettyTable(['Instance Id', 'Instance Name', 'Host Name', 'Private IP', 'State', 'Patch Status'])
table.align = 'l'
for ec2_instance in instances_to_patch:
command_invocation = instance_id_to_command_invocations[ec2_instance.instance_id]
patch_status = command_invocation['Status']
table.add_row([
ec2_instance.instance_id,
ec2_instance.get_tag('Name'),
ec2_instance.private_dns_name_fqdn,
ec2_instance.private_ip_address,
ec2_instance.state,
patch_status
])
print(table)
if failed_count > 0:
self.context.error(f'Patch failed. Please check the patch logs for the instances at {PATCH_LOG}')
else:
self.context.success('Patch executed successfully. Please verify the patch functionality as per release notes / change log.')