HEX
Server: Apache/2.4.6 () PHP/7.4.33
System: Linux chile-dev-app-1 5.4.17-2136.315.5.el7uek.x86_64 #2 SMP Wed Dec 21 19:57:57 PST 2022 x86_64
User: apache (48)
PHP: 7.4.33
Disabled: NONE
Upload Files
File: //lib/python3.6/site-packages/oci_utils/impl/oci_network_config_main.py
#
# Copyright (c) 2017, 2022 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.

"""
This utility assists with configuring network interfaces on Oracle Cloud
Infrastructure instances.  See the manual page for more information.
"""

import argparse
import logging
import sys
import time

import oci_utils
import oci_utils.oci_api
from oci_utils import is_root_user
from oci_utils import where_am_i
from oci_utils.impl.network_helpers import add_mac_to_nm
from oci_utils.impl.network_helpers import ipv_version
from oci_utils.impl.network_helpers import is_valid_ip_address
from oci_utils.impl.oci_resources import OCIVNIC
from oci_utils.impl.row_printer import TablePrinter
from oci_utils.impl.row_printer import TextPrinter
from oci_utils.impl.row_printer import get_row_printer_impl
from oci_utils.impl.row_printer_helpers import IndentPrinter
from oci_utils.impl.row_printer_helpers import get_value_data
from oci_utils.impl.row_printer_helpers import initialise_column_lengths
from oci_utils.impl.row_printer_helpers import list_to_str
from oci_utils.impl.row_printer_helpers import print_data
from oci_utils.impl.row_printer_helpers import print_vnic_data
from oci_utils.vnicutils import VNICUtils

_logger = logging.getLogger("oci-utils.oci-network-config")
VCN_PREFIX = ['ocid1.vcn.oc']
SUBNET_PREFIX = ['ocid1.subnet.oc']
VNIC_PREFIX = ['ocid1.vnic.oc']


class NameWithSpaces(argparse.Action):
    """    Handle argparse arguments containing spaces.
    """
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, " ".join(values))


def def_show_parser(s_parser):
    """
    Define the show subparser.

    Parameters
    ----------
    s_parser: subparsers.

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    show_parser = s_parser.add_parser('show',
                                      description='Shows the current Virtual Interface Cards provisioned in the '
                                                  'Oracle Cloud Infrastructure and configured on this instance. '
                                                  'This is the default action if no options are given.',
                                      help='Shows data of the VNICs configured on this instance.')
    show_parser.add_argument('-I', '--include',
                             metavar='ITEM',
                             action='append',
                             type=uniq_item_validator,
                             dest='include',
                             help='Include an ITEM that was previously excluded using the --exclude option in '
                                  'automatic configuration/deconfiguration.')
    show_parser.add_argument('-X', '--exclude',
                             metavar='ITEM',
                             action='append',
                             type=uniq_item_validator,
                             dest='exclude',
                             help='Persistently exclude ITEM from automatic configuration/deconfiguration. Use '
                                  'the --include option to include the ITEM again.')
    show_parser.add_argument('--details',
                             action='store_true',
                             default=False,
                             help='Display detailed information.')
    show_parser.add_argument('--output-mode',
                             choices=('parsable', 'table', 'json', 'text'),
                             help='Set output mode.',
                             default='table')
    # Display information the way previous version used to do (backward compatibility mode)
    show_parser.add_argument('--no-truncate',
                             action='store_true',
                             default=False,
                             help='Do not truncate value during output ')
    show_parser.add_argument('--compat-output',
                             action='store_true',
                             default=False,
                             help=argparse.SUPPRESS)
    return show_parser


def def_show_vnics_parser(s_parser):
    """
    Define the show_vnics subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    show_vnics_parser = s_parser.add_parser('show-vnics',
                                            description='Shows VNICs information of this instance.',
                                            help='Shows VNICs information of this instance.')
    show_vnics_parser.add_argument('--output-mode',
                                   choices=('parsable', 'table', 'json', 'text'),
                                   help='Set output mode.',
                                   default='table')
    show_vnics_parser.add_argument('--details',
                                   action='store_true',
                                   default=False,
                                   help='Display detailed information')
    show_vnics_parser.add_argument('--ocid',
                                   type=vnic_ocid_validator,
                                   action='store',
                                   metavar='VNIC_OCID',
                                   help='Show information of VNIC matching ocid.')
    show_vnics_parser.add_argument('--name',
                                   type=str,
                                   action=NameWithSpaces,
                                   nargs='+',
                                   metavar='VNIC_NAME',
                                   help='Show information of VNIC matching name.')
    show_vnics_parser.add_argument('--ip-address',
                                   type=str,
                                   action='store',
                                   metavar='PRIMARY_IP',
                                   help='Show information of VNIC matching IP as primary IP')
    show_vnics_parser.add_argument('--no-truncate',
                                   action='store_true',
                                   default=False,
                                   help='Do not truncate value during output ')
    return show_vnics_parser


def def_show_vnics_all_parser(s_parser):
    """
    Define the show_vnics_all subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    show_vnics_all_parser = s_parser.add_parser('show-vnics-all',
                                                description='Show all VNICs information with details of this instance.',
                                                help='Show all VNICs information with details of this instance.')
    show_vnics_all_parser.add_argument('-t', '--truncate',
                                       action='store_true',
                                       help=argparse.SUPPRESS)
    show_vnics_all_parser.add_argument('--output-mode',
                                       choices=('parsable', 'table', 'json', 'text'),
                                       help='Set output mode.',
                                       default='table')
    return show_vnics_all_parser


def def_show_vcn_parser(s_parser):
    """
    Define the show_vcn subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    show_vcn_parser = s_parser.add_parser('show-vcns',
                                          description='Show VCN information in this compartment.',
                                          help='Show VCN information in this compartment.')
    show_vcn_parser.add_argument('--output-mode',
                                 choices=('parsable', 'table', 'json', 'text'),
                                 help='Set output mode.',
                                 default='table')
    show_vcn_parser.add_argument('--details',
                                 action='store_true',
                                 default=False,
                                 help='Display detailed information')
    show_vcn_parser.add_argument('--ocid',
                                 type=vcn_ocid_validator,
                                 action='store',
                                 metavar='VCN_OCID',
                                 help='Show information of VCN matching ocid.')
    show_vcn_parser.add_argument('--name',
                                 type=str,
                                 action=NameWithSpaces,
                                 nargs='+',
                                 metavar='VCN_NAME',
                                 help='Show information of VCN matching name.')
    show_vcn_parser.add_argument('--no-truncate',
                                 action='store_true',
                                 default=False,
                                 help='Do not truncate value during output ')
    return show_vcn_parser


def def_show_subnet_parser(s_parser):
    """
    Define the show_subnet subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    show_subnet_parser = s_parser.add_parser('show-subnets',
                                             description='Show subnet information in this compartment.',
                                             help='Show subnet information in this compartment.')
    show_subnet_parser.add_argument('--output-mode',
                                    choices=('parsable', 'table', 'json', 'text'),
                                    help='Set output mode.',
                                    default='table')
    show_subnet_parser.add_argument('--details',
                                    action='store_true',
                                    default=False,
                                    help='Display detailed information')
    show_subnet_parser.add_argument('--ocid',
                                    type=subnet_ocid_validator,
                                    action='store',
                                    metavar='SUBNET_OCID',
                                    help='Show information of subnet matching ocid.')
    show_subnet_parser.add_argument('--name',
                                    type=str,
                                    action=NameWithSpaces,
                                    nargs='+',
                                    metavar='SUBNET_NAME',
                                    help='Show information of subnet matching name.')
    show_subnet_parser.add_argument('--no-truncate',
                                    action='store_true',
                                    default=False,
                                    help='Do not truncate value during output ')
    return show_subnet_parser


def def_configure_parser(s_parser):
    """
    Define the configure subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    configure_parser = s_parser.add_parser('configure',
                                           description='Add IP configuration for VNICs that are not configured '
                                                       'and delete for VNICs that are no longer provisioned.',
                                           help='Add IP configuration for VNICs that are not configured '
                                                'and delete for VNICs that are no longer provisioned.')
    configure_parser.add_argument('-n', '--namespace',
                                  action='store',
                                  metavar='FORMAT',
                                  help='When configuring, place interfaces in namespace identified by the given '
                                       'format. Format can include $nic and $vltag variables.')
    configure_parser.add_argument('-r', '--start-sshd',
                                  action='store_true',
                                  help='Start sshd in namespace (if -n is present).')
    # Secondary private IP address to use in conjunction configure or deconfigure.'
    # deprecated as redundant with add-secondary-addr and remove-secondary-addr
    configure_parser.add_argument('-S', '--secondary-ip',
                                  nargs=2,
                                  metavar=('IP_ADDR', 'VNIC_OCID'),
                                  dest='sec_ip',
                                  action='append',
                                  help=argparse.SUPPRESS)
    configure_parser.add_argument('-I', '--include',
                                  metavar='ITEM',
                                  action='append',
                                  type=str,
                                  dest='include',
                                  help='Include an ITEM that was previously excluded using the --exclude option in '
                                       'automatic configuration/deconfiguration.')
    configure_parser.add_argument('-X', '--exclude',
                                  metavar='ITEM',
                                  action='append',
                                  type=str,
                                  dest='exclude',
                                  help='Persistently exclude ITEM from automatic configuration/deconfiguration. Use '
                                       'the --include option to include the ITEM again.')
    return configure_parser


def def_unconfigure_parser(s_parser):
    """
    Define the unconfigure subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    unconfigure_parser = s_parser.add_parser('unconfigure',
                                             description='Unconfigure all VNICs (except the primary).',
                                             help='Unconfigure all VNICs (except the primary).')
    # Secondary private IP address to use in conjunction configure or unconfigure.'
    # deprecated as redundant with add-secondary-addr and remove-secondary-addr
    unconfigure_parser.add_argument('-S', '--secondary-ip',
                                    nargs=2,
                                    metavar=('IP_ADDR', 'VNIC_OCID'),
                                    dest='sec_ip',
                                    action='append',
                                    help=argparse.SUPPRESS)
    unconfigure_parser.add_argument('-I', '--include',
                                    metavar='ITEM',
                                    action='append',
                                    type=str, dest='include',
                                    help='Include an ITEM that was previously excluded using the --exclude option '
                                         'in automatic configuration/deconfiguration.')
    unconfigure_parser.add_argument('-X', '--exclude',
                                    metavar='ITEM',
                                    action='append',
                                    type=str,
                                    dest='exclude',
                                    help='Persistently exclude ITEM from automatic configuration/deconfiguration.  '
                                         'Use the --include option to include the ITEM again.')
    return unconfigure_parser


def def_attach_vnic_parser(s_parser):
    """
    Define the attach_vnic subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    attach_vnic_parser = s_parser.add_parser('attach-vnic',
                                             description='Create a new VNIC and attach it to this instance.',
                                             help='Create a new VNIC and attach it to this instance.')
    attach_vnic_parser.add_argument('-I', '--ip-address',
                                    action='store',
                                    metavar='IP_ADDR',
                                    type=ip_address_validator,
                                    help="Private IP to be assigned to the new VNIC.")
    ipv = attach_vnic_parser.add_mutually_exclusive_group()
    ipv.add_argument('-ipv4', '--ipv4',
                     action='store_true',
                     default=False,
                     help='Add an ipv4 address, by default.')
    ipv.add_argument('-ipv6', '--ipv6',
                     action='store_true',
                     default=False,
                     help='Add an ipv6 address.')
    attach_vnic_parser.add_argument('-i', '--nic-index',
                                    action='store',
                                    metavar='INDEX',
                                    type=int,
                                    default=0,
                                    help='Physical NIC card index.')
    attach_vnic_parser.add_argument('--subnet',
                                    action='store',
                                    metavar='SUBNET',
                                    type=str,
                                    # type=subnet_ocid_validator,
                                    help='Connect the new VNIC to the subnet with the given OCID.')
    attach_vnic_parser.add_argument('-n', '--name',
                                    action=NameWithSpaces,
                                    nargs='+',
                                    metavar='NAME',
                                    help='Use NAME as the display name of the new VNIC.')
    attach_vnic_parser.add_argument('--assign-public-ip',
                                    action='store_true',
                                    help='assign a public IP address to the new VNIC.')
    return attach_vnic_parser


def def_detach_vnic_parser(s_parser):
    """
    Define the detach_vnic subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    detach_vnic_parser = s_parser.add_parser('detach-vnic',
                                             description='Detach and delete the VNIC with the given OCID or '
                                                         'primary IP address.',
                                             help='Detach and delete the VNIC with the given OCID or '
                                                  'primary IP address.')
    dg = detach_vnic_parser.add_mutually_exclusive_group(required=True)
    dg.add_argument('-O', '--ocid',
                    action='store',
                    type=vnic_ocid_validator,
                    metavar='OCID',
                    help='Detach the VNIC with the given OCID.')
    dg.add_argument('-I', '--ip-address',
                    action='store',
                    metavar='IP_ADDR',
                    help='Detach the VNIC with the given ip address configured on it.')
    return detach_vnic_parser


def def_add_secondary_addr_parser(s_parser):
    """
    Define the add_secondary_ip subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    add_secondary_addr = s_parser.add_parser('add-secondary-addr',
                                             description="Adds the given secondary private IP.",
                                             help="Adds the given secondary private IP.")
    # add_secondary_addr.add_argument('--ipv',
    #                                 action='store',
    #                                 type=int,
    #                                 choices=[4, 6],
    #                                 default=4,
    #                                 help='Add an ipv4 or ipv6 address.')
    ipv = add_secondary_addr.add_mutually_exclusive_group()
    ipv.add_argument('-ipv4', '--ipv4',
                     action='store_true',
                     default=False,
                     help='Add an ipv4 address, by default.')
    ipv.add_argument('-ipv6', '--ipv6',
                     action='store_true',
                     default=False,
                     help='Add an ipv6 address.')
    add_secondary_addr.add_argument('-I', '--ip-address',
                                    action='store',
                                    metavar='IP_ADDR',
                                    help='Secondary private IP address to to be added.')
    add_secondary_addr.add_argument('-O', '--ocid',
                                    action='store',
                                    type=vnic_ocid_validator,
                                    metavar='OCID',
                                    help='Uses VNIC with the given VNIC id.')
    return add_secondary_addr


def def_remove_secondary_addr_parser(s_parser):
    """
    Define the remove_secondary_ipv4 subparser

    Parameters
    ----------
    s_parser: subparsers

    Returns
    -------
        ArgumentParser: the show subcommand parser.
    """
    remove_secondary_addr = s_parser.add_parser('remove-secondary-addr',
                                                description='Removes the given secondary private IP.',
                                                help='Removes the given secondary private IP.')
    remove_secondary_addr.add_argument('-I', '--ip-address',
                                       action='store',
                                       type=ip_address_validator,
                                       metavar='IP_ADDR',
                                       help='Secondary private addr to to be removed.',
                                       required=True)
    return remove_secondary_addr


def get_arg_parser():
    """
    Parse the command line arguments and return an object representing the
    command line (as returned by argparse's parse_args()).

    Returns
    -------
        The argparse namespace.
    """
    parser = argparse.ArgumentParser(prog='oci-network-config',
                                     description='Utility for configuring network interfaces on an instance running '
                                                 'in the Oracle Cloud Infrastructure.')
    parser.add_argument('--quiet', '-q',
                        action='store_true',
                        help='Suppress information messages.')

    subparser = parser.add_subparsers(dest='command')
    #
    # usage
    subparser.add_parser('usage', description='Displays usage')
    #
    # show
    _ = def_show_parser(subparser)
    #
    # show-vnics
    _ = def_show_vnics_parser(subparser)
    #
    # show-vnics-all
    _ = def_show_vnics_all_parser(subparser)
    #
    # show vncs
    _ = def_show_vcn_parser(subparser)
    #
    # show subnets
    _ = def_show_subnet_parser(subparser)
    #
    # configure
    _ = def_configure_parser(subparser)
    #
    # unconfigure
    _ = def_unconfigure_parser(subparser)
    #
    # attach vnic
    _ = def_attach_vnic_parser(subparser)
    #
    # detach vnic
    _ = def_detach_vnic_parser(subparser)
    #
    #  add secondary ipv4 address
    _ = def_add_secondary_addr_parser(subparser)
    #
    # remove secondary ip address
    _ = def_remove_secondary_addr_parser(subparser)

    return parser


def uniq_item_validator(value):
    """
    Validates unicity by checking that value not already in the list

    Parameter
    ---------
        value : str , option's value
    """
    _logger.debug('%s', where_am_i())
    already_seen = getattr(uniq_item_validator, "_item_seen", [])
    if value in already_seen:
        raise argparse.ArgumentTypeError("Invalid arguments: item both included and excluded: %s" % value)
    already_seen.append(value)
    setattr(uniq_item_validator, "_item_seen", already_seen)

    return value


def validate_vnic_ocid(value):
    """
    Verify the value is a vnic ocid.

    Parameters
    ----------
    value: str
        The ocid

    Returns
    -------
        bool: True on success, False otherwise.
    """
    _logger.debug('%s', where_am_i())
    for prefix in VNIC_PREFIX:
        if value.startswith(prefix):
            return value
    return False


def vnic_ocid_validator(value):
    """
    Validates the value passed is a VNIC ocid

    Parameter:
    ----------
        value : option's value as str
    """
    _logger.debug('%s', where_am_i())
    if validate_vnic_ocid(value):
        return value
    raise argparse.ArgumentTypeError("Invalid arguments: invalid VNIC ocid : %s" % value)


def validate_vcn_ocid(value):
    """
    Verify the value is a vcn ocid.

    Parameters
    ----------
    value: str
        The ocid

    Returns
    -------
        bool: True on success, False otherwise.
    """
    _logger.debug('%s', where_am_i())
    for prefix in VCN_PREFIX:
        if value.startswith(prefix):
            return value
    return False


def vcn_ocid_validator(value):
    """
    Validate the value passed is a Vcn ocid

    Parameter:
    ----------
        value : option's value as str
    """
    _logger.debug('%s', where_am_i())
    if validate_vcn_ocid(value):
        return value
    raise argparse.ArgumentTypeError("Invalid arguments: invalid VCN ocid : %s" % value)


def validate_subnet_ocid(value):
    """
    Verify the value is a subnet ocid.

    Parameters
    ----------
    value: str
        The ocid

    Returns
    -------
        bool: True on success, False otherwise.
    """
    _logger.debug('%s', where_am_i())
    for prefix in SUBNET_PREFIX:
        if value.startswith(prefix):
            return True
    return False


def subnet_ocid_validator(value):
    """
    Validate the value passed is a VNIC ocid

    Parameters:
    -----------
    value : str
        The ocid
    """
    _logger.debug('%s', where_am_i())
    if validate_subnet_ocid(value):
        return value
    raise argparse.ArgumentTypeError("Invalid arguments: invalid subnet ocid : %s" % value)


def ip_address_validator(value):
    """
    Verify if the provided address is a valid IPv[4|6] address.

    Parameters
    ----------
    value: str
        The ip address.

    Returns
    -------
        value: the ip address
    """
    _logger.debug('%s', where_am_i())
    if is_valid_ip_address(value):
        return value
    _logger.debug('Invalid ip address.')
    raise argparse.ArgumentTypeError('Invalid arguments: %s is not a valid IP address.' % value)


def get_oci_api_session():
    """
    Ensure the OCI SDK is available if the option is not None.

    Returns
    -------
        OCISession
            The session or None, if we cannot get one
    """
    _logger.debug('%s', where_am_i())
    session_cache = getattr(get_oci_api_session, "_session", None)
    if session_cache:
        return session_cache

    sess = None
    try:
        _logger.debug('Creating session')
        sess = oci_utils.oci_api.OCISession()
        # it seems that having a client is not enough, we may not be able to query anything on it
        # workaround :
        # try a dummy call to be sure that we can use this session
        if not bool(sess.this_instance()):
            _logger.debug('Returning None session')
            return None
        setattr(get_oci_api_session, "_session", sess)
    except Exception as e:
        _logger.error("Failed to access OCI services: %s", str(e))
    _logger.debug('Returning session')
    return sess


def show_vcn(args):
    """
    Show virtual cloud network data.

    Parameters
    ----------
    args: namespace
        The command line.

    Returns
    -------
        No return value.
    """
    _logger.debug('%s', where_am_i())
    sess = get_oci_api_session()
    if sess is None:
        _logger.error("Failed to get API session.")
        return 1
    vcns = set()
    _vcns = sess.all_vcns()
    if not args.ocid and not args.name:
        vcns.update(_vcns)
    else:
        if args.ocid:
            for v in _vcns:
                if v.get_ocid() == args.ocid:
                    vcns.add(v)
        if args.name:
            for v in _vcns:
                if v.get_display_name() == args.name:
                    vcns.add(v)
    do_show_vcn_information(vcns, args)
    return 0


def do_show_vcn_information(vcn_list, args):
    """
    Display virtual cloud network data.

    Parameters
    ----------
    vcn_list: list of OCIVCN
        The list of vnics.
    args: namespace
        The command line arguments.

    Returns
    -------
        No return value.
    """
    _logger.debug('%s', where_am_i())
    #
    #
    _data_struct = {
        'name':  {'head': 'Name',             'func': 'get_display_name',     'type': 'str', 'collen': 20},
        'ipv4':  {'head': 'IPv4 cidr block',  'func': 'get_ipv4_cidr_block',  'type': 'str', 'collen': 15},
        'ipv6':  {'head': 'IPv6 cidr block',  'func': 'get_ipv6_cidr_blocks', 'type': 'str', 'collen': 20, 'convert': list_to_str}
    }
    _data_struct_detail = {
        'uuid':  {'head': 'OCID',             'func': 'get_ocid',             'type': 'str', 'collen': 32},
        'ipv4s': {'head': 'IPv4 cidr blocks', 'func': 'get_ipv4_cidr_blocks', 'type': 'str', 'collen': 16, 'convert': list_to_str},
        'dns':   {'head': 'DNS label',        'func': 'get_dns_label',        'type': 'str', 'collen': 10},
        'state': {'head': 'State',            'func': 'get_state',            'type': 'str', 'collen': 10},
        'life ': {'head': 'Lifecycle state',  'func': 'get_lifecycle_state',  'type': 'str', 'collen': 17}
    }
    if args.details:
        _data_struct.update(_data_struct_detail)
    #
    # initialise the column widths if no-truncate is set
    if args.no_truncate:
        _data_struct = initialise_column_lengths(_data_struct)
    #
    vcn_data = list()
    for _vcn in vcn_list:
        _vcn_dict = dict()
        for key, _ in _data_struct.items():
            value_data = get_value_data(_vcn, _data_struct[key])
            _vcn_dict[key] = value_data[0]
            val_length = value_data[1]
            if args.no_truncate:
                _data_struct[key]['collen'] = max(val_length, _data_struct[key]['collen'])
        vcn_data.append(_vcn_dict)
    #
    print_data('Virtual Cloud Network Information:',
               _data_struct,
               vcn_data,
               mode=args.output_mode,
               truncate=not args.no_truncate)


def show_subnet(args):
    """
    Show virtual cloud subnet data.

    Parameters
    ----------
    args: namespace
        The command line.

    Returns
    -------
        No return value.
    """
    _logger.debug('%s', where_am_i())
    sess = get_oci_api_session()
    if sess is None:
        _logger.error("Failed to get API session.")
        return 1
    subnets = set()
    _subnets = sess.all_subnets()
    if not args.ocid and not args.name:
        subnets.update(_subnets)
    else:
        if args.ocid:
            for s in _subnets:
                if s.get_ocid() == args.ocid:
                    subnets.add(s)
        if args.name:
            for s in _subnets:
                if s.get_display_name() == args.name:
                    subnets.add(s)
    do_show_subnet_information(subnets, args)
    return 0


def do_show_subnet_information(subnet_list, args):
    """
    Display subnetdata.

    Parameters
    ----------
    subnet_list: list of OCISubnet
        The list of subnets.
    args: namespace
        The command line.

    Returns
    -------
        No return value.
    """
    _logger.debug('%s', where_am_i())
    #
    #
    _data_struct = {
        'name':  {'head': 'Name',               'func': 'get_display_name',                    'type': 'str', 'collen': 20},
        'ipv4':  {'head': 'ipv4 cidr block',    'func': 'get_ipv4_cidr_block',                 'type': 'str', 'collen': 15},
        'ipv6':  {'head': 'ipv6 cidr block',    'func': 'get_ipv6_cidr_block',                 'type': 'str', 'collen': 20}
    }
    _data_struct_detail = {
        'uuid':     {'head': 'OCID',            'func': 'get_ocid',                            'type': 'str',  'collen': 32},
        'vcn':      {'head': 'VCN name',        'func': 'get_vcn_name',                        'type': 'str',  'collen': 20},
        'vcnid':    {'head': 'VCN ocid',        'func': 'get_vcn_id',                          'type': 'str',  'collen': 32},
        'public':   {'head': 'Public',          'func': 'is_public_ip_on_vnic_allowed',        'type': 'bool', 'collen': 6},
        'publicin': {'head': 'Public ingress',  'func': 'is_internet_ingress_on_vnic_allowed', 'type': 'bool', 'collen': 14},
        'dns':      {'head': 'DNS label',       'func': 'get_dns_label',                       'type': 'str',  'collen': 11},
        'domain':   {'head': 'Domain name',     'func': 'get_domain_name',                     'type': 'str',  'collen': 20},
        'life ':    {'head': 'Lifecycle state', 'func': 'get_lifecycle_state',                 'type': 'str',  'collen': 17}
    }
    #
    # remove vcn uuid if output mode is table
    if args.output_mode in ['table']:
        _data_struct_detail.pop('vcnid', None)
    #
    # add details if necessary
    if args.details:
        _data_struct.update(_data_struct_detail)
    #
    # initialise the column widths
    if args.no_truncate:
        _data_struct = initialise_column_lengths(_data_struct)
    #
    subnet_data = list()
    for _subn in subnet_list:
        _subn_dict = dict()
        for key, _ in _data_struct.items():
            value_data = get_value_data(_subn, _data_struct[key])
            _subn_dict[key] = value_data[0]
            val_length = value_data[1]
            if args.no_truncate:
                _data_struct[key]['collen'] = max(val_length, _data_struct[key]['collen'])
        subnet_data.append(_subn_dict)
    #
    print_data('Subnet Information:', _data_struct, subnet_data, mode=args.output_mode, truncate=not args.no_truncate)


def show_vnics(args):
    """
    Show Virtual Network Interface data.

    Parameters
    ----------
    args: namespace
        The command line.

    Returns
    -------
        No return value.
    """
    _logger.debug('%s', where_am_i())
    sess = get_oci_api_session()
    if sess is None:
        _logger.error("Failed to get API session.")
        return 1
    vnics = set()
    _vnics = sess.this_instance().all_vnics()
    if not args.ocid and not args.name and not args.ip_address:
        vnics.update(_vnics)
    else:
        if args.ocid:
            for v in _vnics:
                if v.get_ocid() == args.ocid:
                    vnics.add(v)
        if args.name:
            for v in _vnics:
                if v.get_display_name() == args.name:
                    vnics.add(v)
        if args.ip_address:
            for v in _vnics:
                if v.get_private_ip() == args.ip_address:
                    vnics.add(v)
    do_show_vnics_information(vnics, args)
    return 0


def get_conf_states(vnics):
    """
    Get configuration state of the vnic,

    Parameters
    ----------
    vnics: OCIVNIC
        The OCIVNIC object.

    Returns
    -------
        dict: The configuration states.
    """
    _logger.debug('%s', where_am_i())
    _network_config = VNICUtils().get_network_config()
    conf_states = dict()
    for vnic in vnics:
        priv_ip = vnic.get_private_ip()
        for network in _network_config:
            if priv_ip == network['ADDR']:
                conf_states[priv_ip] = network['CONFSTATE']
                break
    return conf_states


def do_show_vnics_information(vnics, args):
    """
    Show given VNIC information.

    Parameters:
    ----------
        vnics : OCIVNIC instances
        mode : the output mode as str (text,json,parsable)
        details : display detailed information ?
    """
    _logger.debug('%s', where_am_i())
    _data_struct = {
        'name':       {'head': 'Name',       'func':  'get_display_name', 'type': 'str', 'collen': 20},
        'privateip':  {'head': 'Private IP', 'func':  'get_private_ip',   'type': 'str', 'collen': 10},
        'mac':        {'head': 'MAC',        'func':  'get_mac_address',  'type': 'str', 'collen': 17},
        'config':     {'head': 'Config',     'item':  'get_conf_state',   'type': 'str', 'collen': 6}
        # this function fails intentionally, data is completed below
        # todo: implement decent way to get this data.
    }
    _data_struct_detail = {
        'ocid':               {'head': 'OCID',                'func': 'get_ocid',                            'type': 'str',  'collen': 32},
        'primary':            {'head': 'Primary',             'func': 'is_primary',                          'type': 'bool', 'collen': 7},
        'subnetname':         {'head': 'Subnet',              'func': ['get_subnet', 'get_display_name'],    'type': 'str',  'collen': 15},
        'subnetid':           {'head': 'Subnet OCID',         'func': ['get_subnet', 'get_ocid'],            'type': 'str',  'collen': 32},
        'subnetcidr':         {'head': 'Subnet cidr',         'func': ['get_subnet', 'get_ipv4_cidr_block'], 'type': 'str',  'collen': 15},
        'state':              {'head': 'State',               'func': 'get_state',                           'type': 'str',  'collen': 6},
        'mic':                {'head': 'NIC',                 'func': 'get_nic_index',                       'type': 'str',  'collen': 3},
        'public':             {'head': 'Public IP',           'func': 'get_public_ip',                       'type': 'str',  'collen': 15},
    }

    _data_struct_secondary = {
        'privateip':     {'head': 'Private IP', 'func': 'get_address', 'type': 'str', 'collen': 15},
        'ocid':          {'head': 'OCID',       'func': 'get_ocid',    'type': 'str', 'collen': 32},
    }
    # remove vcn uuid if output mode is table
    if args.output_mode in ['table']:
        _data_struct_detail.pop('subnetid', None)

    if args.details:
        _data_struct.update(_data_struct_detail)
    #
    # initialise the column widths
    if args.no_truncate:
        _data_struct = initialise_column_lengths(_data_struct)
        _data_struct_secondary = initialise_column_lengths(_data_struct_secondary)
    #
    conf_states = get_conf_states(vnics)
    #
    vnic_data = list()
    for _vnic in vnics:
        _vnic_dict = dict()

        for key, _ in _data_struct.items():
            value_data = get_value_data(_vnic, _data_struct[key])
            _vnic_dict[key] = value_data[0]
            val_length = value_data[1]
            if args.no_truncate:
                _data_struct[key]['collen'] = max(val_length, _data_struct[key]['collen'])
        #
        # conf state
        _vnic_dict['config'] = conf_states[_vnic_dict['privateip']]
        val_length = len(_vnic_dict['config'])
        if args.no_truncate:
            _data_struct['config']['collen'] = max(val_length, _data_struct['config']['collen'])
        #
        if args.details:
            _priv_ipv4s = _vnic.all_private_ipv4_ips()
            if len(_priv_ipv4s) > 0:
                ipv4_data = list()
                for _priv_ipv4 in _priv_ipv4s:
                    _priv_ipv4_dict = dict()
                    for key, _ in _data_struct_secondary.items():
                        value_data = get_value_data(_priv_ipv4, _data_struct_secondary[key])
                        _priv_ipv4_dict[key] = value_data[0]
                        val_length = value_data[1]
                        if args.no_truncate:
                            _data_struct_secondary[key]['collen'] \
                                = max(val_length, _data_struct_secondary[key]['collen'])
                    ipv4_data.append(_priv_ipv4_dict)
                _vnic_dict['ipv4'] = ipv4_data

            _priv_ipv6s = _vnic.all_private_ipv6_ips()
            if len(_priv_ipv6s) > 0:
                ipv6_data = list()
                for _priv_ipv6 in _priv_ipv6s:
                    _priv_ipv6_dict = dict()
                    for key, _ in _data_struct_secondary.items():
                        value_data = get_value_data(_priv_ipv6, _data_struct_secondary[key])
                        _priv_ipv6_dict[key] = value_data[0]
                        val_length = value_data[1]
                        if args.no_truncate:
                            _data_struct_secondary[key]['collen'] \
                                = max(val_length, _data_struct_secondary[key]['collen'])
                    ipv6_data.append(_priv_ipv6_dict)
                _vnic_dict['ipv6'] = ipv6_data

        vnic_data.append(_vnic_dict)
    #
    print_vnic_data('Virtual Network Interface Information:', _data_struct, _data_struct_secondary, vnic_data,
                    mode=args.output_mode,
                    truncate=not args.no_truncate)


def do_show_information(vnic_utils, mode, details=False):
    """
    Display network information

    Parameters
    ----------
        vnic_utils : instance of VNICUtils

        mode : str
            output mode (text,parsable etc...)
        details : bool
            display detailed information ?
    """
    _logger.debug('%s', where_am_i())
    sess = get_oci_api_session()
    if sess is None:
        raise Exception("Failed to get API session.")

    vnics = sess.this_instance().all_vnics()
    network_config = vnic_utils.get_network_config()

    def _display_subnet(interface):
        """
        Return the network subnet.

        Parameters
        ----------
        interface: dict
            The interface.

        Returns
        -------
            str: the network subnet or if the interface matches a vnic, the OCI vnic subnet.
        """
        _logger.debug('%s', where_am_i())
        if interface['VNIC']:
            for v in vnics:
                if v.get_ocid() == interface['VNIC']:
                    v_subnet = [interface['SPREFIX4'], interface['SBITS4']]
                    return '%s/%s (%s)' % (v_subnet[0], v_subnet[1], v.get_subnet().get_display_name())
        return '%s/%s' % (interface['SPREFIX4'], interface['SBITS4'])

    def _get_vnic_name(interface):
        """
        Get the vnic name.

        Parameters
        ----------
        interface: dict
            The interface.

        Returns
        -------
            str: the vnic name if the interface matches a vnic, else '-'
        """
        _logger.debug('%s', where_am_i())
        if interface['VNIC']:
            for v in vnics:
                if v.get_ocid() == interface['VNIC']:
                    return v.get_display_name()
        return '-'

    def _get_hostname(interface):
        """
        Return the interfaces' hostname.

        Parameters
        ----------
        interface: dict
            The interface.

        Returns
        -------
            str: the vnic hostname if the interface matches a vnic, else '-'
        """
        _logger.debug('%s', where_am_i())
        if interface['VNIC']:
            for v in vnics:
                if v.get_ocid() == interface['VNIC']:
                    v_hostname = v.get_hostname()
                    return 'None' if v_hostname is None else v_hostname
        return '-'

    _logger.debug('%s', where_am_i())
    _cols = ['State', 'Link', 'Status', 'IP address', 'VNIC', 'MAC']
    _col_name = ['state', 'link', 'status', 'ipaddress', 'vnic', 'mac']
    _cols_details = ['Hostname', 'Subnet', 'Router IP', 'Namespace', 'Index', 'VLAN tag', 'VLAN']
    _col_detail_name = ['hostname', 'subnet', 'routerip4', 'namespace', 'index', 'vlantag', 'vlan']

    if details:
        _cols = [*_cols, *_cols_details]
        _col_name = [*_col_name, *_col_detail_name]

    _cols_len = list()
    for col in _cols:
        _cols_len.append(len(col))
    _collen = dict(zip(_col_name, _cols_len))

    vnic_data = list()
    for item in network_config:
        _nic_data = dict()
        # state
        _nic_data['state'] = item['CONFSTATE']
        _collen['state'] = max(len(_nic_data['state']), _collen['state'])
        # link
        _nic_data['link'] = item['IFACE']
        _collen['link'] = max(len(_nic_data['link']), _collen['link'])
        # status
        _nic_data['status'] = item['STATE']
        _collen['status'] = max(len(_nic_data['status']), _collen['status'])
        # ipaddress
        _nic_data['ipaddress'] = item['ADDR']
        _collen['ipaddress'] = max(len(_nic_data['ipaddress']), _collen['ipaddress'])
        # vnic
        _nic_data['vnic'] = _get_vnic_name(item)
        _collen['vnic'] = max(len(_nic_data['vnic']), _collen['vnic'])
        # mac
        _nic_data['mac'] = item['MAC']
        _collen['mac'] = max(len(_nic_data['mac']), _collen['mac'])
        if details:
            # hostname
            _nic_data['hostname'] = _get_hostname(item)
            _collen['hostname'] = max(len(_nic_data['hostname']), _collen['hostname'])
            # subnet
            _nic_data['subnet'] = _display_subnet(item)
            _collen['subnet'] = max(len(_nic_data['subnet']), _collen['subnet'])
            # routerip4
            _nic_data['routerip4'] = item['VIRTRT4']
            _collen['routerip4'] = max(len(_nic_data['routerip4']), _collen['routerip4'])
            # namespace
            _nic_data['namespace'] = item['NS']
            _collen['namespace'] = max(len(_nic_data['namespace']), _collen['namespace'])
            # index
            _nic_data['index'] = item['IND']
            _collen['index'] = max(len(_nic_data['index']), _collen['index'])
            # vlantag
            _nic_data['vlantag'] = item['VLTAG']
            _collen['vlantag'] = max(len(_nic_data['vlantag']), _collen['vlantag'])
            # vlan
            _nic_data['vlan'] = item['VLAN']
            _collen['vlan'] = max(len(_nic_data['vlan']), _collen['vlan'])
        vnic_data.append(_nic_data)

    _columns = list()
    for i, _ in enumerate(_cols):
        _columns.append([_cols[i], _collen[_col_name[i]]+2, _col_name[i]])

    printerKlass = get_row_printer_impl(mode)
    printer = printerKlass(title='Network configuration', columns=_columns)

    printer.printHeader()
    for item in vnic_data:
        printer.printRow(item)
        printer.rowBreak()
    printer.printFooter()
    printer.finish()


def compat_show_vnics_information():
    """
    Show the current VNIC configuration of the instance based on

    Returns
    -------
       No return value.
    """
    _logger.debug('%s', where_am_i())

    def _display_subnet(_, vn):
        """Return subnet display name of this vnic
        """
        return vn.get_subnet().get_display_name()

    def _display_secondary_ip_subnet(_, privip):
        _sn = privip.get_subnet()
        return '%s (%s)' % (_sn.get_display_name(), _sn.get_ipv4_cidr_block())

    def _display_vnic_name(_, vn):
        if vn.is_primary():
            return '%s (primary)' % vn.get_display_name()
        return vn.get_display_name()

    _logger.debug('%s', where_am_i())
    sess = get_oci_api_session()
    if sess is None:
        _logger.error("Failed to get API session.")
        return
    _logger.debug('Getting instance ')
    inst = sess.this_instance()
    if inst is None:
        _logger.error("Failed to get information from OCI.")
        return
    _logger.debug('Getting all vnics ')
    vnics = inst.all_vnics()
    _logger.debug('Got for printing')

    _title = 'VNIC configuration for instance %s:' % inst.get_display_name()

    _columns = (['Name', 32, _display_vnic_name],
                ['Hostname', 25, 'get_hostname'],
                ['MAC', 17, 'get_mac_address'],
                ['Public IP', 15, 'get_public_ip'],
                ['Private IP(s)', 15, 'get_private_ip'],
                ['Subnet', 18, _display_subnet],
                ['OCID', 90, 'get_ocid'])

    printer = TextPrinter(title=_title, columns=_columns, column_separator='')
    ips_printer = TextPrinter(title='Private IP addresses:',
                              columns=(['IP address', 15, 'get_address'],
                                       ['OCID', '90', 'get_ocid'],
                                       ['Hostname', 25, 'get_hostname'],
                                       ['Subnet', 24, _display_secondary_ip_subnet]),
                              printer=IndentPrinter(3))

    printer.printHeader()
    for vnic in vnics:
        printer.printRow(vnic)
        #
        # __GT__ to check if compatible mode works with ipv6:
        # --> no for now, the IPv6 oci-sdk object has no attribute 'is_primary' and a few more.
        _all_p_ips = vnic.all_private_ips()
        if len(_all_p_ips) > 1:
            # _all_p_ips include the primary we won't print (>1)
            ips_printer.printHeader()
            for p_ip in _all_p_ips:
                if not p_ip.is_primary():
                    # primary already displayed
                    ips_printer.printRow(p_ip)
            printer.rowBreak()
            ips_printer.printFooter()
            ips_printer.finish()
    printer.printFooter()
    printer.finish()


def show_os_network_config(vnic_utils):
    """
    Display the current network interface configuration as well as the VNIC configuration from OCI.

    Parameters
    ----------
    vnic_utils :
        The VNIC configuration instance.

    Returns
    -------
        No return value.
    """
    def _get_subnet(_, interface):
        return '%s/%s' % (interface['SPREFIX4'], interface['SBITS4'])

    _logger.debug('%s', where_am_i())
    ret = vnic_utils.get_network_config()

    _title = "Operating System level network configuration:"
    _columns = (['CONFIG', 6, 'CONFSTATE'],
                ['ADDR', 15, 'ADDR'],
                ['SUBNET', 15, 'SPREFIX4'],
                ['BITS', 5, 'SBITS4'],
                ['VIRTROUTER', 15, 'VIRTRT4'],
                ['NS', 10, 'NS'],
                ['IND', 4, 'IND'],
                ['IFACE', 15, 'IFACE'],
                ['VLTAG', 5, 'VLTAG'],
                ['VLAN', 13, 'VLAN'],
                ['STATE', 6, 'STATE'],
                ['MAC', 17, 'MAC'],
                ['VNIC ID', 90, 'VNIC'])
    printer = TablePrinter(title=_title, columns=_columns, column_separator='', text_truncate=False)

    printer.printHeader()
    for item in ret:
        printer.printRow(item)
    printer.printFooter()
    printer.finish()


def do_detach_vnic(detach_options):
    """
    Detach and delete the VNIC with the given ocid or primary ip address

    Parameters
    ----------
    detach_options : namespace
        The argparse namespace.

    Returns
    -------
        No return value on success;.

    Raises
    ------
        Exception
            if session cannot be acquired
            if the VNIC cannot be detached

    """
    _logger.debug('%s', where_am_i())
    sess = get_oci_api_session()
    if sess is None:
        raise Exception("Failed to get API session.")
    vnics = sess.this_instance().all_vnics()
    for vnic in vnics:
        v_ocid = vnic.get_ocid()
        v_ip = vnic.get_private_ip()
        if v_ocid == detach_options.ocid or v_ip == detach_options.ip_address:
            if not vnic.is_primary():
                _logger.info('Detaching VNIC %s [%s]', v_ip, v_ocid)
                macaddress = vnic.get_mac_address()
                vnic.detach()
                _logger.info('VNIC [%s] is detached.', v_ocid)
                add_mac_to_nm(macaddress)
                _logger.debug('Added mac address %s back to nm.', macaddress)
                break
            raise Exception("The primary VNIC cannot be detached.")
    else:
        _logger.error('VNIC %s [%s] is not attached to this instance.', detach_options.ip_address, detach_options.ocid)


def get_subnet_ocid_from_arg(this_session, subnet_arg):
    """
    Get the ocid for the subnet data provided on the command line.

    Parameters
    ----------
    this_session: OCISession
        The oci-sdk session.
    subnet_arg: str
        The subnet data from the command line.

    Returns
    -------
        str: the ocid of the subnet, if found.
    """
    _logger.debug('%s: %s', where_am_i(), subnet_arg)
    #
    # subnet specified in command line
    if not validate_subnet_ocid(subnet_arg):
        #
        # subnet name
        subnets = this_session.find_subnets(subnet_arg)
        if len(subnets) == 0:
            raise Exception("No subnet matching %s found" % subnet_arg)
        if len(subnets) > 1:
            _logger.error("More than one subnet matching %s found:\n", subnet_arg)
            for sn in subnets:
                _logger.error("   %s\n", sn.get_display_name())
            raise Exception("More than one subnet matching")
        return subnets[0].get_ocid()
    #
    # subnet ocid
    return subnet_arg


def get_subnet_ocid_from_ip(this_instance, ip_arg):
    """
    Get the ocid for the subnet the provided ip address belongs to.

    Parameters
    ----------
    this_instance: OCIInstance
        The instance object.

    ip_arg: str
        The ip address.

    Returns
    -------
        str: the ocid of the subnet, if found.
    """
    _logger.debug('%s: %s', where_am_i(), ip_arg)
    subnet_id = None
    _all_subnets = [v.get_subnet() for v in this_instance.all_vnics()]
    for subn in _all_subnets:
        if subn.is_suitable_for_ip(ip_arg):
            subnet_id = subn.get_ocid()
    if subnet_id is None:
        raise Exception('Cannot find suitable subnet for ip %s' % ip_arg)
    return subnet_id


def do_create_vnic(create_options):
    """
    Create and attach a VNIC to this instance.

    Parameters
    ----------
    create_options:
        The VNIC configuration instance.

    Returns
    -------
        No return value on success; errors out with return value 1 otherwise.

    Raises
    ------
        Exception
            if session cannot be acquired
    """
    _logger.debug('%s: %s', where_am_i(), create_options)
    # needs the OCI SDK installed and configured
    session = get_oci_api_session()
    if session is None:
        raise Exception("Failed to get API session.")

    the_instance = session.this_instance()

    subnet_id = None
    if create_options.subnet:
        #
        # subnet data provided on command line.
        subnet_id = get_subnet_ocid_from_arg(session, create_options.subnet)
    else:
        #
        # no subnet data on the command line.
        #
        # if private ip provided, pick up subnet with matching IP
        # else pick the subnet of the primary vnic
        if create_options.ip_address:
            #
            # private ip provided, look for subnet the ip belongs to.
            if is_valid_ip_address(create_options.ip_address):
                subnet_id = get_subnet_ocid_from_ip(the_instance, create_options.ip_address)
            else:
                raise Exception('Invalid ip address format.')
        else:
            #
            # No subnet nor ip provided, use the primary vnic.
            _primary_v = [v for v in the_instance.all_vnics() if v.is_primary()][0]
            subnet_id = _primary_v.get_subnet_id()
    _logger.debug('Subnet ocid used: %s', subnet_id)

    try:
        vnic = the_instance.attach_vnic(private_ip=create_options.ip_address,
                                        assign_public_ip=create_options.assign_public_ip,
                                        subnet_id=subnet_id,
                                        nic_index=create_options.nic_index,
                                        display_name=create_options.name,
                                        ipv=create_options.ipv)
    except Exception as e:
        # raise Exception('Failed to create VNIC: %s' % str(e)) from e
        raise ValueError('%s' % str(e)) from e

    if not isinstance(vnic, OCIVNIC):
        raise ValueError('Failed to attach VNIC %s' % create_options.name)

    #
    #
    public_ip = vnic.get_public_ip()
    if public_ip is not None:
        _logger.info('Creating VNIC: %s (public IP %s)', vnic.get_private_ip(), public_ip)
    else:
        _logger.info('Creating VNIC: %s', vnic.get_private_ip())


def do_add_private_ipv4(vnic_utils, vnic, ip_address):
    """
    Add an ipv4 address to a vnic.

    Parameters
    ----------
    vnic_utils: VNICUtils
        The VNICUtils helper instance.
    vnic: OCIVNIC
        The vnic data.
    ip_address: str
        The ipaddress to add.

    Returns
    -------
        tuple: (ipv6, ocid)
    """
    _logger.debug('%s', where_am_i())
    try:
        priv_ipv4 = vnic.add_private_ipv4(private_ip=ip_address)
    except Exception as e:
        _logger.debug('Failed to provision private IPv4: %s ', str(e))
        raise Exception('%s ' % str(e)) from e

    _logger.info('Provisioning secondary private IPv4: %s', priv_ipv4.get_address())
    vnic_utils.add_private_ip(priv_ipv4.get_address(), vnic.get_ocid())
    return priv_ipv4.get_address(), vnic.get_ocid()


def do_add_private_ipv6(vnic_utils, vnic, ip_address):
    """
    Add an ipv6 address to a vnic.

    Parameters
    ----------
    vnic_utils: VNICUtils
        The VNICUtils helper instance.
    vnic: OCIVNIC
        The vnic data.
    ip_address: str
        The ipaddress to add.

    Returns
    -------
        tuple: (ipv6, ocid)
    """
    _logger.debug('%s', where_am_i())
    try:
        priv_ipv6 = vnic.add_private_ipv6(private_ipv6=ip_address)
    except Exception as e:
        _logger.debug('Failed to provision private IPv6: %s ', str(e))
        raise Exception('%s ' % str(e)) from e

    _logger.info('Provisioning secondary private IPv6: %s', priv_ipv6.get_address())
    vnic_utils.add_private_ip(priv_ipv6.get_address(), vnic.get_ocid())
    return priv_ipv6.get_address(), vnic.get_ocid()


def do_add_secondary_addr(vnic_utils, add_options):
    """
    Add a secondary private IP for an existing VNIC.

    Parameters
    ----------
    vnic_utils : VNICUtils
        The VNICUtils helper instance.
    add_options : namespace
        The argparse namespace.

    Returns
    -------
        tuple
            (private_IP,vnic_ocid) for the new IP on success; errors out with
            return value 1 otherwise.

    Raises
    ------
        Exception
            On any error.
    """
    _logger.debug('%s', where_am_i())
    # needs the OCI SDK installed and configured
    sess = get_oci_api_session()
    if sess is None:
        raise Exception("Failed to get API session.")

    if add_options.ocid:
        vnic = sess.get_vnic(vnic_id=add_options.ocid)
        if vnic is None:
            raise Exception("VNIC not found: %s" % add_options.ocid)
    else:
        vnics = sess.this_instance().all_vnics()
        if len(vnics) > 1:
            _logger.error("More than one VNIC found.Use the --ocid option to select the one to add a secondary IP for:")
            for vnic in vnics:
                _logger.error("   %s: %s", vnic.get_private_ip(), vnic.get_ocid())
            raise Exception("Too many VNICs found")
        vnic = vnics[0]

    if add_options.ipv == 4:
        priv_ip, ip_ocid = do_add_private_ipv4(vnic_utils, vnic, add_options.ip_address)
    elif add_options.ipv == 6:
        priv_ip, ip_ocid = do_add_private_ipv6(vnic_utils, vnic, add_options.ip_address)
    else:
        raise Exception('Invalid ip version: %d' % add_options.ipv)

    return priv_ip, ip_ocid


def do_remove_secondary_addr(vnic_utils, delete_options):
    """
    Delete a secondary private IP

    Parameters
    ----------
    vnic_utils :
        The VNIC configuration instance.
    delete_options :
        The argparse namespace.

    Returns
    -------
        No return value on success; errors out with return value 1 otherwise.

    Raises
    ------
    Exception
        error getting session
    """
    _logger.debug('%s', where_am_i())
    ip_addr = delete_options.ip_address
    #
    # ipv4 or ipv6; valid ip address is verified by the ip_address_validator.
    ip_version = ipv_version(ip_addr)
    #
    # needs the OCI SDK installed and configured
    sess = get_oci_api_session()
    if sess is None:
        raise Exception("Failed to get API session.")
    #
    # find the private IP
    priv_ip = sess.this_instance().find_private_ip(ip_addr)
    if priv_ip is None:
        raise Exception("Secondary private IP not found: %s" % ip_addr)
    #
    # cannot delete primary ip from a vnic
    if priv_ip.is_primary():
        raise Exception("Cannot delete IP %s, it is the primary private address of the VNIC." % ip_addr)
    #
    # get the vnic ocid
    vnic_id = priv_ip.get_vnic_ocid()
    #
    # delete the private ip from the vnic in OCI
    if not priv_ip.delete():
        raise Exception('Failed to delete secondary private IP %s' % ip_addr)
    #
    # cleanup on instance
    _logger.info('Deconfigure secondary private IP %s', ip_addr)
    # delete from vnic_info and de-configure the interface
    return vnic_utils.del_private_ip(ip_addr, vnic_id)


def get_vnic_utils(args):
    """
    Collect the VNIC data.

    Parameters
    ----------
    args: namespace
        The command lind arguments.

    Returns
    -------
        VNICUtils: the data.
    """
    _logger.debug('%s', where_am_i())
    vnic_utls = VNICUtils()

    if 'exclude' in args and args.exclude:
        for exc in args.exclude:
            vnic_utls.exclude(exc)

    if 'include' in args and args.include:
        for inc in args.include:
            vnic_utls.include(inc)
    return vnic_utls


def show_network(vnic_utls, args):
    """
    Display the network information.

    Parameters
    ----------
    vnic_utls: VNICUtils
        The vnic data.
    args: namespace
        The command line arguments.

    Returns
    -------
        int: 0 on success, 1 otherwise.
    """
    _logger.debug('%s', where_am_i())
    #
    # for compatibility mode, oci-network-config show should provide the same output as oci-network-config --show;
    # if output-mode is specified, compatiblity requirement is dropped.
    showerror = False
    if args.compat_output:
        compat_show_vnics_information()
    else:
        try:
            do_show_information(vnic_utls, args.output_mode, args.details)
        except Exception as e:
            _logger.debug('Cannot show information', exc_info=True)
            _logger.error('Cannot show information: %s', str(e))
            showerror = True
    if args.output_mode == 'table':
        show_os_network_config(vnic_utls)
    return 1 if showerror else 0


def get_ipv_version(args):
    """
    Get the ip version from the commandline, default is 4.

    Parameters
    ----------
    args: namespace
        command line.
    Returns
    -------
        int: the IP version.
    """
    if args.ipv4:
        return 4
    if args.ipv6:
        return 6
    return 4


def ipv_not_supported(ipv):
    """
    Write a messages attaching a vnic with primary ipv6 address is not (yet) supported.
    Parameters
    ----------
    ipv: int
        The ipv version.
    Returns
    -------
        No return value.
    """
    _logger.debug('%s', where_am_i())
    if ipv == 6:
        _logger.warning('Attaching a vnic with a primary ipv%d address is not yet supported by OCI.', ipv)
        sys.exit(1)


def main():
    """
    Main

    Returns
    -------
        int
            0 on success;
            1 on failure.
    """
    parser = get_arg_parser()
    args = parser.parse_args()

    if args.quiet:
        _logger.setLevel(logging.WARNING)

    if not args.command:
        parser.print_help()
        return 1

    if args.command == 'usage':
        parser.print_help()
        return 0

    if not is_root_user():
        _logger.error("This program needs to be run with root privileges.")
        return 1

    vnic_utils = get_vnic_utils(args)

    if args.command == 'show':
        return show_network(vnic_utils, args)

    if args.command == 'show-vnics':
        return show_vnics(args)

    if args.command == 'show-vnics-all':
        args.no_truncate = True
        args.details = True
        args.ocid = None
        args.ip_address = None
        args.name = None
        return show_vnics(args)

    if args.command == 'show-vcns':
        return show_vcn(args)

    if args.command == 'show-subnets':
        return show_subnet(args)

    if args.command == 'attach-vnic':
        args.ipv = get_ipv_version(args)
        #
        # OCI does not support creating a vnic with an ipv6 address only.
        ipv_not_supported(args.ipv)
        if 'nic_index' in args and args.nic_index != 0:
            if not get_oci_api_session().this_shape().startswith("BM"):
                _logger.error('--nic-index option ignored when not runnig on Bare Metal type of shape')
                return 1
        try:
            do_create_vnic(args)
        except Exception as e:
            _logger.debug('Cannot create the VNIC', exc_info=True)
            _logger.error('%s', str(e))
            return 1
        # apply config of newly created vnic
        time.sleep(25)
        vnic_utils = VNICUtils()
        vnic_utils.auto_config(None, deconfigured=False)

    if args.command == 'detach-vnic':
        try:
            do_detach_vnic(args)
        except Exception as e:
            _logger.debug('Cannot detach VNIC', exc_info=True, stack_info=True)
            # _logger.error('Cannot detach VNIC: %s', str(e))
            _logger.error('%s', str(e))
            return 1
        # if we are here session is alive: no check
        if get_oci_api_session().this_shape().startswith("BM"):
            # in runnning on BM some cleanup is needed on the host
            vnic_utils.auto_config(None, deconfigured=False)

    if args.command == "add-secondary-addr":
        args.ipv = get_ipv_version(args)
        if args.ip_address:
            if is_valid_ip_address(args.ip_address):
                args.ipv = ipv_version(args.ip_address)
            else:
                _logger.error('Invalid IP address provided: %s', args.ip_address)
                return 1
        try:
            ip, vnic_id = do_add_secondary_addr(vnic_utils, args)
            _logger.info("IP %s has been assigned to vnic %s.", ip, vnic_id)
        except Exception as e:
            _logger.debug('%s', str(e), stack_info=True)
            # _logger.error('Failed to add private IP: %s', str(e))
            _logger.error('%s', str(e))
            return 1

    if args.command == "remove-secondary-addr":
        try:
            (ret, out) = do_remove_secondary_addr(vnic_utils, args)
            if ret != 0:
                raise Exception('Cannot delete ip: %s' % out)
        except Exception as e:
            _logger.debug('Failed to delete private IP: %s', str(e), stack_info=True)
            # _logger.error('Failed to delete private IP: %s', str(e))
            _logger.error('%s', str(e))
            return 1

    if 'namespace' in args and args.namespace:
        try:
            vnic_utils.set_namespace(args.namespace)
        except Exception as e:
            _logger.debug('Failed to set namespace: %s', str(e), stack_info=True)
            _logger.error('Failed to set namespace: %s', str(e))

    if 'start_sshd' in args and args.start_sshd:
        try:
            vnic_utils.set_sshd(args.start_sshd)
        except Exception as e:
            _logger.debug('Failed to start sshd: %s', str(e), stack_info=True)
            _logger.error('Failed to start sshd: %s', str(e))

    if args.command == 'configure':
        try:
            vnic_utils.auto_config(args.sec_ip)
            _logger.info('Configured ')
        except Exception as e:
            _logger.debug('Failed to configure network: %s', str(e), stack_info=True)
            _logger.error('Failed to configure network: %s', str(e))

    if args.command == 'unconfigure':
        try:
            vnic_utils.auto_deconfig(args.sec_ip)
            _logger.info('Unconfigured ')
        except Exception as e:
            _logger.debug('Failed to unconfigure network: %s', str(e), stack_info=True)
            _logger.error('Failed to unconfigure network: %s', str(e))

    return 0


if __name__ == "__main__":
    sys.exit(main())