Managing Scheduled Jobs and Work Requests

Describes how to manage OS Management scheduled jobs and work requests.

Scheduled Jobs

When you use the OS Management service to manage updates on a managed instance or managed instance group, you have full control over when actions takes place.

If you specify that an action take place immediately, the OS Management service creates a work request.

If you specify that an action is to take place at a particular date and time, the OS Management service creates a scheduled job. There are two basic modes for scheduled jobs:

  • A scheduled job in which the job executes once.

    You can schedule one-time jobs for tasks such as installing an update or a set of updates. These tasks represent activities that are specific to a one-time event. For example, you can schedule a one-time job installing a specific package version, like Python, to support an application. When scheduling these actions, you have the option to perform the action immediately or to choose a Custom Schedule where you can select the Date and Time on which to schedule a one-time job.

  • A scheduled job in which the job recurs at a specified interval.

    You can schedule recurring jobs for tasks such as installing all available updates for a managed instance group. For example, you can schedule a job to install all security updates every week at a certain time. When scheduling these actions, you have the option to perform the action immediately or to choose a Custom Schedule where you can select the Date and Time to initially run the job and then optionally set the job to Repeat at a specified interval (Hourly, Daily, Weekly, or Monthly).

When the scheduled date and time are reached, one or more work requests are created to perform the action. You have full control over scheduled jobs, to run them immediately, to delete them, or to skip a recurring job. The OS Management service maintains a complete history of scheduled jobs and their associated work requests.

Note

For more information about tasks that support scheduled jobs, see Managing Linux Packages and Managing Windows Updates.

Work Requests

Actions such as installing or removing updates are asynchronous and initiate work requests. You can use the work request to track the status of these operations, including the ability to see why an action failed. The OS Management service maintains a complete history of work requests on managed instances or managed instance groups.

Work Request States

The work request states are:

Accepted
The request is in the work request queue to be processed.
In Progress
The work request is being processed.
Succeeded
The work request has been processed successfully.
Failed
The work request has not been processed successfully. You can look at the work request logs to identify the issues, and then troubleshoot it.
Canceling
The work request is being canceled.
Canceled
The work request has been canceled.
Note

The OS Management service cleans up work requests that are older than 2 weeks, which have completed successfully or failed. Any work request that has not started or is in progress is not cleaned up.

Using the Console

To manage scheduled jobs
  1. Open the navigation menu and click Compute. Under OS Management, click Scheduled Jobs.
  2. In the List Scope section, select the compartment that contains the scheduled jobs.
  3. Next to a scheduled job, click the Actions icon (three dots) and select an action:
    • View Details: See the instances affected by the job and the action to be performed.
    • Run Now: Override the schedule and perform the job immediately.
    • Skip: (Recurring scheduled jobs only) Delay the scheduled job until its next scheduled occurrence.
    • Delete: Cancel the scheduled job.

Using the API

For information about using the API and signing requests, see REST APIs and Security Credentials. For information about SDKs, see Software Development Kits and Command Line Interface.

Scheduled Jobs

Use these API operations for working with scheduled jobs:

Work Requests

Use these API operations for analyzing work requests:

For a full list of API operations available for the OS Management service, see OS Management API.

Using the Python SDK to Generate Compliance Reports

This section shows how to run a security compliance report using an example Python script (compliance_report.py) that leverages the OS Management APIs. The example Python script generates a security compliance report, either across a tenancy or per compartment, for all managed instances that are missing security updates.

Note

The Python SDK enables you to write code to manage Oracle Cloud Infrastructure resources. For more information, see Python SDK.
Tip

For a video demonstration showing how to run a security compliance report, see Video: Create a compliance report for Linux instances.
compliance_report.py

#!/usr/bin/env python3

#
# Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at
#  http://oss.oracle.com/licenses/upl
#

import os
import sys
import oci
import time
import logging
import logging.handlers

from argparse import ArgumentParser, ArgumentError, Namespace, SUPPRESS
from oci.os_management import OsManagementClient
from oci.identity import IdentityClient

PROGRAM_NAME = os.path.basename(sys.argv[0]).replace('.py', '')
PROGRAM_VERSION = '0.1.0'


def setup_logger(enable_logfile=False, verbose=False, debug=False):
    class LevelsFilter(logging.Filter):
        def __init__(self, levels, name=''):
            logging.Filter.__init__(self, name)
            self.levels = levels

        def filter(self, record):
            if record.levelno in self.levels:
                return True
            return False

    flat_formatter = logging.Formatter('{message}', style='{')
    level_formatter = logging.Formatter('{levelname:8s}: {message}', style='{')
    log_file_handler = None
    if enable_logfile or debug:
        try:
            formatter = logging.Formatter('{asctime} - {name} - {levelname}({module}:{lineno}) - {message}', style='{')
            log_file_handler = logging.handlers.RotatingFileHandler('{0}.log'.format(PROGRAM_NAME),
                                                                    mode='a',
                                                                    maxBytes=1024 * 1024,
                                                                    backupCount=3)
            log_file_handler.setFormatter(formatter)
            log_file_handler.setLevel(logging.NOTSET)
        except IOError:
            pass

    logger = logging.getLogger(PROGRAM_NAME)
    stdout_handler = logging.StreamHandler(stream=sys.stdout)
    stdout_handler.setFormatter(flat_formatter)
    logger.setLevel(logging.ERROR)
    if verbose:
        stdout_handler.addFilter(LevelsFilter([logging.INFO]))
        logger.setLevel(logging.INFO)
    if debug:
        log_file_handler.setFormatter(level_formatter)
        log_file_handler.addFilter(LevelsFilter([logging.DEBUG,
                                                 logging.INFO,
                                                 logging.WARNING,
                                                 logging.ERROR,
                                                 logging.CRITICAL]))
        logger.setLevel(logging.DEBUG)
    stderr_handler = logging.StreamHandler(stream=sys.stderr)
    stderr_handler.setFormatter(level_formatter)
    stderr_handler.addFilter(LevelsFilter([logging.WARNING, logging.ERROR, logging.CRITICAL]))
    if log_file_handler is not None:
        logger.addHandler(log_file_handler)
    logger.addHandler(stdout_handler)
    logger.addHandler(stderr_handler)
    return logger


class OCIClients(object):
    def __init__(self, options):
        self.compartment_id = options.compartment_id
        if options.use_instance_principles:
            config = {}
            if options.region is not None:
                config['region'] = options.region
            signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
            if self.compartment_id is None:
                self.compartment_id = signer.tenancy_id
            self.osms_client = OsManagementClient(config,
                                                  timeout=(10, 600),
                                                  retry_strategy=oci.retry.DEFAULT_RETRY_STRATEGY,
                                                  signer=signer)
            self.iam_client = IdentityClient(config,
                                             timeout=(10, 600),
                                             retry_strategy=oci.retry.DEFAULT_RETRY_STRATEGY,
                                             signer=signer)
        else:
            config = oci.config.from_file(file_location=options.config_file, profile_name=options.config_profile)
            if options.region is not None:
                config['region'] = options.region
            if self.compartment_id is None:
                self.compartment_id = config.get('tenancy')
            else:
                config['compartment'] = self.compartment_id
            self.osms_client = OsManagementClient(config,
                                                  timeout=(10, 600),
                                                  retry_strategy=oci.retry.DEFAULT_RETRY_STRATEGY)
            self.iam_client = IdentityClient(config,
                                             timeout=(10, 600),
                                             retry_strategy=oci.retry.DEFAULT_RETRY_STRATEGY)

    def get_osms_client(self):
        return self.osms_client

    def get_iam_client(self):
        return self.iam_client


class Data(object):
    def __init__(self):
        self.managed_instance_group_id = None
        self.total_instances = 0
        self.vulnerable_instances = []

    def show_overview(self):
        print()
        print('        Patch Compliance Report Sample')
        print('        ==============================')
        print()
        if self.managed_instance_group_id is not None:
            print('Managed Instance Group ID: {0}'.format(self.managed_instance_group_id))
        vulnerable_instances = len(self.vulnerable_instances) if len(self.vulnerable_instances) > 0 else 'None'
        linking_verb = 'are' if len(self.vulnerable_instances) > 1 else 'is'
        plural = 's' if self.total_instances > 1 else ''
        print('\nDetected out of {0} managed instance{1}, {2} {3} '
              'missing security patches!\n'.format(self.total_instances, plural, vulnerable_instances, linking_verb))

    def show_details(self):
        for instance in self.vulnerable_instances:
            print('Managed Instance {0} ({1})'.format(instance.get('display_name'), instance.get('id')))
            print(' has the following outstanding security patches:')
            for update in instance.get('security_updates', []):
                print('  {0}'.format(update.get('display_name')))
                if update.get('related_cves', None) is not None:
                    print('      CVEs:', end='')
                    num_cve = 0
                    num_cves = len(update.get('related_cves', []))
                    cve_line = ''
                    for cve in update.get('related_cves', []):
                        num_cve += 1
                        cve_line = '{0} {1:<17}'.format(cve_line, cve + ',')
                        if len(cve_line) >= 65:
                            if num_cve < num_cves:
                                print(cve_line.rstrip(' '))
                                print('           ', end='')
                            else:
                                print(cve_line.rstrip(', '))
                            cve_line = ''
                    if cve_line != '':
                        print(cve_line.rstrip(', '))
            print()


def find_all_compartments(iam_client, compartment_id):
    LOGGER.info('Find sub compartments')
    compartment_ids = [compartment_id]
    if compartment_id.startswith('ocid1.tenancy.'):
        c_response = iam_client.list_compartments(compartment_id, compartment_id_in_subtree=True)
        compartment_ids.extend([c.id for c in c_response.data
                                if c.lifecycle_state == 'ACTIVE' and c.name != 'ManagedCompartmentForPaaS'])
    else:
        compartment_ids.extend(list_compartments(iam_client, compartment_ids))
    return compartment_ids


def list_compartments(iam_client, compartments):
    if compartments is None:
        return []
    sub_compartments = []
    for compartment in compartments:
        LOGGER.debug('List Compartment: {0}'.format(compartment))
        c_response = iam_client.list_compartments(compartment)
        sub_compartments.extend([c.id for c in c_response.data
                                 if c.lifecycle_state == 'ACTIVE' and c.name != 'ManagedCompartmentForPaaS'])
        sub_names = [c.name for c in c_response.data
                     if c.lifecycle_state == 'ACTIVE' and c.name != 'ManagedCompartmentForPaaS']
        LOGGER.debug('Sub Compartments: {0}'.format(sub_names))
        sub_compartments.extend(list_compartments(iam_client, sub_compartments))
    return sub_compartments


def query_managed_instance_group(osms_client, managed_instance_group_id):
    LOGGER.info('Retrieving Managed Instance Group ({0}) info'.format(managed_instance_group_id))
    mig_response = osms_client.get_managed_instance_group(managed_instance_group_id)
    total_instances, vuln_instances = query_managed_instances(osms_client, mig_response.data.managed_instances)
    return total_instances, vuln_instances


def query_compartment(osms_client, compartment_id):
    LOGGER.info('Retrieving Managed Instance list from compartment "{0}"'.format(compartment_id))
    request_options = {'limit':      10,
                       'sort_by':    'TIMECREATED',
                       'sort_order': 'ASC',
                       }
    managed_instances = []
    next_page = None
    has_next_page = True
    while has_next_page:
        if next_page is not None:
            request_options['page'] = next_page
        elif request_options.get('page', False):
            request_options.pop('page')
        try:
            mil_response = osms_client.list_managed_instances(compartment_id)  # , **request_options)
        except oci.exceptions.ServiceError as service_error:
            LOGGER.info('Service Exception "{0}")'.format(service_error.code))
            LOGGER.debug('OCI Request ID: "{0}"'.format(service_error.request_id))
            raise
        if mil_response is None:
            raise RuntimeError('Unable to retrieve updates from compartment {0}'.format(compartment_id))
        has_next_page = mil_response.has_next_page
        next_page = mil_response.next_page
        managed_instances.extend(mil_response.data)
    total_instances, vuln_instances = query_managed_instances(osms_client, managed_instances)
    return total_instances, vuln_instances


def query_managed_instances(osms_client, managed_instances):
    vuln_instances = []
    total_instances = len(managed_instances)
    for mi in managed_instances:
        LOGGER.info('Retrieving Managed Instance "{0}" info'.format(mi.display_name))
        mi_response = osms_client.get_managed_instance(mi.id)
        mi_data = mi_response.data
        LOGGER.debug('Managed Instance: {0}'.format(mi_data))
        linux_mi = mi_data.os_family == 'LINUX'
        available_security_updates = 0
        security_updates = []
        updates = None
        if mi_data.updates_available > 0:
            LOGGER.info('Retrieving Update info for Managed Instance "{0}"'.format(mi.display_name))
            request_options = {'limit':      10,
                               'sort_by':    'TIMECREATED',
                               'sort_order': 'ASC',
                               }
            next_page = None
            has_next_page = True
            while has_next_page:
                if next_page is not None:
                    request_options['page'] = next_page
                elif request_options.get('page', False):
                    request_options.pop('page')
                try:
                    if linux_mi:
                        updates = osms_client.list_available_updates_for_managed_instance(mi.id, **request_options)
                    else:
                        updates = osms_client.list_available_windows_updates_for_managed_instance(mi.id,
                                                                                                  **request_options)
                except oci.exceptions.ServiceError as service_error:
                    LOGGER.info('Service Exception "{0}")'.format(service_error.code))
                    LOGGER.debug('OCI Request ID: "{0}"'.format(service_error.request_id))
                if updates is None:
                    raise RuntimeError('Unable to retrieve updates from {0}'.format(mi.display_name))
                has_next_page = updates.has_next_page
                next_page = updates.next_page
                for update in updates.data:
                    LOGGER.debug('Update: {0}'.format(update))
                    if update.update_type == 'SECURITY':
                        available_security_updates += 1
                        if linux_mi:
                            security_updates.append({'display_name': update.display_name,
                                                     'related_cves': update.related_cves})
                        else:
                            security_updates.append({'display_name': update.display_name})
            if available_security_updates > 0:
                managed_instance = {
                        'compartment_id':             mi_data.compartment_id,
                        'display_name':               mi_data.display_name,
                        'id':                         mi_data.id,
                        'is_reboot_required':         mi_data.is_reboot_required,
                        'last_boot':                  mi_data.last_boot,
                        'last_checkin':               mi_data.last_checkin,
                        'managed_instance_groups':    mi_data.managed_instance_groups,
                        'os_family':                  mi_data.os_family,
                        'os_kernel_version':          mi_data.os_kernel_version,
                        'os_name':                    mi_data.os_name,
                        'os_version':                 mi_data.os_version,
                        'status':                     mi_data.status,
                        'updates_available':          mi_data.updates_available,
                        'security_updates_available': available_security_updates,
                        'security_updates':           security_updates
                }
                vuln_instances.append(managed_instance)
    return total_instances, vuln_instances


def main(argv=None):
    if argv is None:
        argv = sys.argv
    program_version_message = '{0} {1}'.format(PROGRAM_NAME, PROGRAM_VERSION)
    program_description = 'OS Management Reporting'
    options = Namespace()
    try:
        the_parser = ArgumentParser(description=program_description)
        select_grp = the_parser.add_mutually_exclusive_group()
        authop_grp = the_parser.add_mutually_exclusive_group()
        the_parser.add_argument('--compartment-ocid', '-c',
                                dest='compartment_id',
                                action='store',
                                default=None,
                                required=True,
                                help="Compartment to run query against, defaults to the root compartment")
        the_parser.add_argument('--region', '-r',
                                dest='region',
                                action='store',
                                default=None,
                                required=False,
                                help="Region to run query against")
        authop_grp.add_argument('--use-instance-principles', '-I',
                                dest='use_instance_principles',
                                action='store_true',
                                default=False,
                                required=False,
                                help="Authenticate using Instance Principles")
        select_grp.add_argument('--managed-instance-group-ocid', '-g',
                                dest='managed_instance_group_id',
                                action='store',
                                default=None,
                                required=False,
                                help="Managed Instance Group to query")
        authop_grp.add_argument('--oci-config', '-C',
                                dest='config_file',
                                action='store',
                                default=os.path.join('~', '.oci', 'config'),
                                required=False,
                                help="Oracle Cloud Infrastructure config file")
        the_parser.add_argument('--oci-config-profile', '-P',
                                dest='config_profile',
                                action='store',
                                default='DEFAULT',
                                required=False,
                                help="Oracle Cloud Infrastructure config profile to use")
        the_parser.add_argument('--show-details', '-d',
                                dest='show_details',
                                action='store_true',
                                default=False,
                                required=False,
                                help='Show detailed report')
        select_grp.add_argument('--recursive', '-R',
                                dest='scan_sub_compartments',
                                action='store_true',
                                default=False,
                                required=False,
                                help='Recursively scan sub-compartments')
        the_parser.add_argument('--verbose', '-v',
                                dest='verbose',
                                action='store_true',
                                default=False,
                                required=False,
                                help='Enable verbose mode')
        the_parser.add_argument("--version",
                                action="version",
                                version=program_version_message)
        the_parser.add_argument("--debug",
                                dest='debug_enabled',
                                action='store_true',
                                default=False,
                                required=False,
                                help=SUPPRESS)
        the_parser.parse_args(args=argv[1:], namespace=options)
    except ArgumentError:
        return 1
    # noinspection PyGlobalUndefined
    global LOGGER
    LOGGER = setup_logger(verbose=options.verbose, debug=options.debug_enabled)
    oci_clients = OCIClients(options)
    data = Data()
    if options.managed_instance_group_id is not None:
        total_instances, vulnerable_instances = query_managed_instance_group(oci_clients.osms_client,
                                                                             options.managed_instance_group_id)
        data.total_instances += total_instances
        data.vulnerable_instances.extend(vulnerable_instances)
        data.managed_instance_group_id = options.managed_instance_group_id
        LOGGER.debug('MIG Query: Total Instances {0}, Vulnerable Instances {1}'.format(total_instances,
                                                                                       vulnerable_instances))
    else:
        compartment_ids = [options.compartment_id]
        if options.scan_sub_compartments:
            compartment_ids = find_all_compartments(oci_clients.iam_client, options.compartment_id)
        for compartment_id in compartment_ids:
            total_instances, vulnerable_instances = query_compartment(oci_clients.osms_client, compartment_id)
            data.total_instances += total_instances
            data.vulnerable_instances.extend(vulnerable_instances)
            LOGGER.debug('Compartment ({0}) Instances: Total {1}, Vulnerable {2}'.format(compartment_id,
                                                                                         total_instances,
                                                                                         len(vulnerable_instances)))
    data.show_overview()
    if options.show_details:
        data.show_details()
    return 0


if __name__ == '__main__':
    try:
        sys.exit(main())
    except KeyboardInterrupt:
        print('\n{0}: Cancelled by user'.format(PROGRAM_NAME))
        sys.exit(127)
To run a security compliance report using the compliance_report.py script

  1. Ensure that Python is installed.

    For example, run the following command on Oracle Linux:

    sudo yum install python3 -y
  2. In a file editor such as vi, create the sample Python script used for this example, compliance_report.py.

    For example:

    sudo vi compliance_report.py
  3. Copy the contents of compliance_report.py to your file.
  4. Make the script executable.

    For example:

    chmod +x compliance_report.py
  5. Run the script to generate a security compliance report.

    To see the options available for running the script, run the compliance_report.py script with --help option.

    # python3 compliance_report.py --help
    usage: compliance_report.py [-h] --compartment-ocid COMPARTMENT_ID
                                [--region REGION] [--use-instance-principles]
                                [--managed-instance-group-ocid MANAGED_INSTANCE_GROUP_ID]
                                [--oci-config CONFIG_FILE]
                                [--oci-config-profile CONFIG_PROFILE]
                                [--show-details] [--recursive] [--verbose]
                                [--version]
    
    OS Management Reporting
    
    optional arguments:
      -h, --help            show this help message and exit
      --compartment-ocid COMPARTMENT_ID, -c COMPARTMENT_ID
                            Compartment to run query against, defaults to the root
                            compartment
      --region REGION, -r REGION
                            Region to run query against
      --use-instance-principles, -I
                            Authenticate using Instance Principles
      --managed-instance-group-ocid MANAGED_INSTANCE_GROUP_ID, -g MANAGED_INSTANCE_GROUP_ID
                            Managed Instance Group to query
      --oci-config CONFIG_FILE, -C CONFIG_FILE
                            Oracle Cloud Infrastructure config file
      --oci-config-profile CONFIG_PROFILE, -P CONFIG_PROFILE
                            Oracle Cloud Infrastructure config profile to use
      --show-details, -d    Show detailed report
      --recursive, -R       Recursively scan sub-compartments
      --verbose, -v         Enable verbose mode
      --version             show program's version number and exit
    Important

    When using the compliance_report.py script, note the following usage guidelines:

    • You must have the proper permissions to read and query the compartment to successfully run this example script.
    • The --compartment-ocid option is required. For Compartment_ID, specify either the tenancy OCID (the root compartment) or the OCID for a compartment in the tenancy.
    • The --use-instance-principles option sets the script to authenticate using Oracle Cloud Infrastructure instance principles. If you do not use this option, you must have a profile set up in ~/.oci/config.
    • The --recursive option generates the report for the compartment and any subcompartments within that region. You cannot use the --recursive option with managed instance groups. If you are running the report at the compartment level, do not use the --recursive option.
    • When using the --show details option, the output displays a detailed listing of the specific security patches that are missing for each managed instance. If you run the script without this option, the output highlights only which managed instances are missing updates and how many updates are missing.
    • The --region option is optional.
      • If you have specified a specific region in ~/.oci/config, you can use the --region option to override this region with the one that you specify for this parameter.
      • If you are using instance principles, you can run the script in one region and use the --region option to target a different region.

    The following example shows how to generate a sample report using the compliance_report.py script:

    # python3 compliance_report.py --use-instance-principles --compartment-ocid=ocid1.tenancy.oc1..<unique_ID> --recursive --show-details --region=eu-zurich-1
    
            Patch Compliance Report Sample
            ==============================
    
    
    Detected out of 7 managed instances, 7 are missing security patches!
    
    Managed Instance sqa-test-prod-zurich-win2019srvstd (ocid1.instance.oc1.eu-zurich-1..<unique_ID>)
     has the following outstanding security patches:
      2020-04 Cumulative Update for Windows Server 2019 (1809) for x64-based Systems (KB4549949)
    
    Managed Instance sqa-test-prod-zurich-win2016srvstd (ocid1.instance.oc1.eu-zurich-1..<unique_ID>)
     has the following outstanding security patches:
      Security Intelligence Update for Windows Defender Antivirus - KB2267602 (Version 1.315.54.0)
      2020-04 Servicing Stack Update for Windows Server 2016 for x64-based Systems (KB4550994)
    
    Managed Instance sqa-test-prod-zurich-win2012srvstd (ocid1.instance.oc1.eu-zurich-1..<unique_ID>)
     has the following outstanding security patches:
      2020-04 Security Monthly Quality Rollup for Windows Server 2012 R2 for x64-based Systems (KB4550961)
    
    Managed Instance sqatest-ol6-0 (ocid1.instance.oc1.eu-zurich-1..<unique_ID>)
     has the following outstanding security patches:
      libcurl
          CVEs: CVE-2019-5482
      python
          CVEs: CVE-2018-20852
      kernel-uek-firmware
          CVEs: CVE-2018-5953,    CVE-2019-18806,   CVE-2020-10942
      python-libs
          CVEs: CVE-2018-20852
      sudo
          CVEs: CVE-2019-18634
      curl
          CVEs: CVE-2019-5482
      kernel-uek
          CVEs: CVE-2018-5953,    CVE-2019-18806,   CVE-2020-10942
      kernel
          CVEs: CVE-2017-1000371, CVE-2019-17666
      libicu
          CVEs: CVE-2020-10531
    
    Managed Instance sqatest-ol7-0 (ocid1.instance.oc1.eu-zurich-1..<unique_ID>)
     has the following outstanding security patches:
      kernel-tools-libs
          CVEs: CVE-2015-9289,    CVE-2017-17807,   CVE-2018-19985,   CVE-2018-20169,
                CVE-2018-7191,    CVE-2019-10207,   CVE-2019-10638,   CVE-2019-10639,
                CVE-2019-11190,   CVE-2019-11884,   CVE-2019-12382,   CVE-2019-13233,
                CVE-2019-13648,   CVE-2019-14283,   CVE-2019-15916,   CVE-2019-16746,
                CVE-2019-18660,   CVE-2019-3901,    CVE-2019-9503
      sqlite
          CVEs: CVE-2019-13734
      python
          CVEs: CVE-2018-20852,   CVE-2019-16056
      kernel-uek
          CVEs: CVE-2018-5953,    CVE-2019-18806,   CVE-2019-18809,   CVE-2020-10942
      python-libs
          CVEs: CVE-2018-20852,   CVE-2019-16056
      file
          CVEs: CVE-2018-10360
      file-libs
          CVEs: CVE-2018-10360
      curl
          CVEs: CVE-2019-5436
      libcurl
          CVEs: CVE-2019-5436
      mariadb-libs
          CVEs: CVE-2019-2737,    CVE-2019-2739,    CVE-2019-2740,    CVE-2019-2805
      kernel-tools
          CVEs: CVE-2015-9289,    CVE-2017-17807,   CVE-2018-19985,   CVE-2018-20169,
                CVE-2018-7191,    CVE-2019-10207,   CVE-2019-10638,   CVE-2019-10639,
                CVE-2019-11190,   CVE-2019-11884,   CVE-2019-12382,   CVE-2019-13233,
                CVE-2019-13648,   CVE-2019-14283,   CVE-2019-15916,   CVE-2019-16746,
                CVE-2019-18660,   CVE-2019-3901,    CVE-2019-9503
    
    Managed Instance sqatest-ol8-0 (ocid1.instance.oc1.eu-zurich-1..<unique_ID>)
     has the following outstanding security patches:
      sudo
          CVEs: CVE-2019-18634
      grub2-pc
          CVEs: CVE-2019-14865
      kernel
          CVEs: CVE-2019-15030,   CVE-2019-15031,   CVE-2019-18660,   CVE-2019-19527
      grub2-pc-modules
          CVEs: CVE-2019-14865
      sqlite-libs
          CVEs: CVE-2019-13734
      grub2-tools
          CVEs: CVE-2019-14865
      grub2-tools-minimal
          CVEs: CVE-2019-14865
      grub2-common
          CVEs: CVE-2019-14865
      bpftool
          CVEs: CVE-2019-15030,   CVE-2019-15031,   CVE-2019-18660,   CVE-2019-19527
      nss
          CVEs: CVE-2019-11745
      kernel-modules
          CVEs: CVE-2019-15030,   CVE-2019-15031,   CVE-2019-18660,   CVE-2019-19527
      libarchive
          CVEs: CVE-2019-18408
      grub2-tools-extra
          CVEs: CVE-2019-14865
      kernel-tools
          CVEs: CVE-2019-15030,   CVE-2019-15031,   CVE-2019-18660,   CVE-2019-19527
      nss-util
          CVEs: CVE-2019-11745
      kernel-tools-libs
          CVEs: CVE-2019-15030,   CVE-2019-15031,   CVE-2019-18660,   CVE-2019-19527
      nss-sysinit
          CVEs: CVE-2019-11745
    
    Managed Instance wb-zrh-cli-test (ocid1.instance.oc1.eu-zurich-1..<unique_ID>)
     has the following outstanding security patches:
      Security Intelligence Update for Windows Defender Antivirus - KB2267602 (Version 1.315.38.0)
      2020-04 Cumulative Update for Windows Server 2019 (1809) for x64-based Systems (KB4549949)