The Oracle Cloud Infrastructure (OCI) Python Software Development Kit (SDK) offers an easy and convenient way to work with cloud resources. In this article you can learn how to iterate over a set of compute Virtual Machine (VM) instances with the aim of printing the VM’s display name, lifecycle status, and public IP address (if present) in a tabular format. A cloud VM’s public IP address is associated with a virtual Network Interface Card (vNIC). vNICs are linked to compute VMs by means of vNIC attachments. Finding a public IP address, or any IP address for that matter, requires locating the VM’s vNIC attachment followed by a loop over all vNICs and getting their properties.

Requirements

You should have installed and configured the Python SDK on your machine. Quite a few options exist for you to authenticate against OCI, either via API-keys, instance/resource principal and more. This example assumes you are running the Python code locally on your laptop, authenticating via an API key. The article was written on MacOS using Python 3.9 and the Oracle-PythonSDK/2.93.1.

Let’s see some Code already!

The code example is deliberately kept simple, strictly for readability.

Preamble

The preamble consists of the usual pointer to the Python interpreter, a little bit of documentation about the script’s purpose followed by a few imports – all of which are already present after installing the SDK so no further calls to pip install are needed. Finally a global variable, all_instance_metadata is defined as an empty array in preparation of holding each instance’s metadata

 
#!/usr/bin/python3
#
# blogpost.py
# 
# A small python script demonstrating how to iterate over the
# API response in Python-OCI SDK.
#
# This example lists the public IP address for any given compute
# VM along its display name, lifecycle state and OCID
# 
# Usage:
# ./blogpost.py <COMPARTMENT OCID
#
import sys
import oci
import argparse
from terminaltables import AsciiTable

# -------------------------------------------------- global variables

all_instance_metadata = [];

Command line parsing is very easy thanks to Python’s argparse module. The only positional parameter needed is the compartment’s OCID.

 
# -------------------------------------------------- parsing command line arguments
parser = argparse.ArgumentParser(
    prog = "python3 blogpost.py",
    description = "List name, OCID, lifecycle state and public IP for every compute VM in a compartment"
)

parser.add_argument(
    "compartment_ocid",
    help="the compartment OCID to be scanned"
)

args = parser.parse_args()
compartment_ocid = args.compartment_ocid

Once the command line argument is parsed it’s time for some action. In the first step the script loads the configuration and validates it.

 
# step 1) load the config. In this example API-key authentication is used. 
# Alternative authentication methods exist as well, have a look at
# https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdk_authentication_methods.htm
# for more details
config = oci.config.from_file()
oci.config.validate_config(config)

After the configuration has been parsed and validated the actual action can begin by listing all the compute VM instances in the compartment provided on the command line:

 
# step 2) list all the compute instances in a compartment. Many additional
# filter options exist, see 
# https://docs.oracle.com/en-us/iaas/tools/python/2.93.1/api/core/client/oci.core.ComputeClient.html#oci.core.ComputeClient.list_instances
# for more details
compute_client = oci.core.ComputeClient(config)
try:
    vm_instances = compute_client.list_instances(
        compartment_id = compartment_ocid
    ).data
except oci.exceptions.ServiceError as s:
    print(f"ERR: failed to obtain a list of compute VM instances due to '{s.message}'")
    sys.exit(1)

if len(vm_instances) == 0:
    print (f"ERR: no compute VMs found in compartment {compartment_ocid}")
    sys.exit(2)

Note that the “data” array is accessed immediately and assigned to vm_instances. With the list of VM instances available the next step is to iterate over all of them:

 
# step 3) Public IP addresses are part of the instance virtual network
# interface card (NIC). Each vNIC is attached to a compute instance
# by means of a vNIC attachement. These must be obtained using a virtual
# network client
virtual_network_client = oci.core.VirtualNetworkClient(config)

# iterate over all the instances found in step 2
for vm in vm_instances:

    # this dict stores the relevant instance details
    instance_info = {
        "display_name": vm.display_name,
        "id": vm.id,
        "lifecycle_state": vm.lifecycle_state,
        "public_ips": [ ]
    }

    # skip terminated instances
    if vm.lifecycle_state == "TERMINATED":
        continue
    
    # get the VM's vNIC attachements. You could add a check for an error in this
    # call but this isn't done for the sake of readability.
    vnic_attachments = compute_client.list_vnic_attachments(
        compartment_id=vm.compartment_id,
        instance_id=vm.id
    ).data

    # get a list of vNICs from the vNIC attachement. Most often you
    # find a single vNIC, but it's possible to have multiple.
    vnics = [virtual_network_client.get_vnic(va.vnic_id).data for va in vnic_attachments]
    for vnic in vnics:
        if vnic.public_ip:
            instance_info["public_ips"].append(vnic.public_ip) 
    
    all_instance_metadata.append(instance_info)

Within the loop a Python dict named instance_info is populated with details concerning the VM instance. With all the information gathered the dict is appended to the global all_instance_metadata array. With the loop completed the final step is to print the information in tabular format:

 
# step 4) construct the output table
table_data = [
    [ "Display Name", "Lifecycle Status", "Public IPs", "Oracle Cloud ID"]
]

for row in all_instance_metadata:
    table_data.append(
        [row["display_name"], row["lifecycle_state"], ", ".join(row["public_ips"]), row["id"]]
    )

# step 5: print the table
print(AsciiTable(table_data).table)

That’s it! If you run the script and provide a suitable compartment OCID it prints the information gathered in a nice, familiar-looking AsciiTable on your terminal:

 
+--------------+------------------+------------+-----------------------------------...---+
| Display Name | Lifecycle Status | Public IPs | Oracle Cloud ID                   ...   |
+--------------+------------------+------------+-----------------------------------...---+
| vm_inst1     | STOPPING         | 1.2.3.4    | ocid1.instance.oc1.eu-frankfurt-1....ja |
| vm_inst2     | STARTING         | 2.3.4.5    | ocid1.instance.oc1.eu-frankfurt-1....3a |
| vm_inst3     | RUNNING          |            | ocid1.instance.oc1.eu-frankfurt-1....ha |
+--------------+------------------+------------+-----------------------------------...---+

The third VM instance in the list, vm_inst3 resides in a private subnet, therefore it cannot have a public IP by definition.

Happy scripting!