def patch_app()

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.')