Introduction to Arista PyeAPI

The Python Client for eAPI (pyeapi) is a Python library that simplifies working with Arista eAPI, removing the need to deal with the specifics of its implementation. It's straightforward to configure and use. In this blog post, we'll look at how to install pyeapi and go through some examples.

If you're familiar with Arista's eAPI, you know that you can browse to the device's IP in a browser, run commands, and get the output directly. You can also achieve the same result using Python, but it typically requires understanding which libraries to use and how to construct the REST API requests.

However, pyeapi simplifies all of this. You don't need to worry about what's happening behind the scenes. Below is a screenshot of running show vlan command via the REST API, and in the following examples, we'll see how to get the same output using pyeapi.

PyeAPI Installation

To install pyeapi, you can use pip, which is the standard package manager for Python. It's a good practice to use a virtual environment (venv) to keep your dependencies isolated and avoid conflicts with other projects. First, create and activate a virtual environment. Once your virtual environment is activated, you can install pyeapi using the pip command.

# Create a virtual environment named 'venv'
python3 -m venv venv

# Activate the virtual environment
source venv/bin/activate
pip install pyeapi

Before you start using pyeapi please make sure the device is configured to accept API requests. Here is a sample config that I previously used. You may not need this if your device is already in production but if something doesn't work, use this as a reference.

security pki key generate rsa 4096 key.pem
security pki certificate generate self-signed cert.pem key key.pem validity 3650 parameters common-name access-01

management security
   ssl profile MY_PROFILE
  	tls versions 1.2
  	certificate cert.pem key key.pem

management api http-commands
   protocol https ssl profile MY_PROFILE
   no shut

Connecting to a Single Arista Device

To kick things off, let's try to connect to a single Arista device using the script below.

import pyeapi

connection = pyeapi.client.connect(
    transport="https",
    host="192.168.100.212",
    username="admin",
    password='admin',
    port=443
)

node = pyeapi.client.Node(connection)
response = node.enable(["show vlan"])
print(response)

In this script, we start by importing the pyeapi library. We then create a connection to the device using the connect() method, specifying parameters such as transport type, host IP, username, password, and port.

Once the connection is established, we create a Node object, which allows us to interact with the device. In this case, we use the enable() method to send the command "show vlan" and then print the response.

[{'command': 'show vlan', 'result': {'vlans': {'1': {'name': 'default', 'dynamic': False, 'status': 'active', 'interfaces': {}}, '10': {'name': 'finance', 'dynamic': False, 'status': 'active', 'interfaces': {'Ethernet1': {'privatePromoted': False, 'blocked': None}, 'Ethernet2': {'privatePromoted': False, 'blocked': None}, 'Ethernet5': {'privatePromoted': False, 'blocked': None}}}, '20': {'name': 'sales', 'dynamic': False, 'status': 'active', 'interfaces': {'Ethernet1': {'privatePromoted': False, 'blocked': None}, 'Ethernet2': {'privatePromoted': False, 'blocked': None}}}, '30': {'name': 'cctv', 'dynamic': False, 'status': 'active', 'interfaces': {'Ethernet1': {'privatePromoted': False, 'blocked': None}, 'Ethernet2': {'privatePromoted': False, 'blocked': None}, 'Ethernet6': {'privatePromoted': False, 'blocked': None}}}}, 'sourceDetail': ''}, 'encoding': 'json'}]

If you want to make the output more readable, you can use the json.dumps() method to pretty-print the response. This will format the JSON output in a structured way, making it easier to understand.

import json

print(json.dumps(response, indent=2))
[
  {
    "command": "show vlan",
    "result": {
      "vlans": {
        "1": {
          "name": "default",
          "dynamic": false,
          "status": "active",
          "interfaces": {}
        },
        "10": {
          "name": "finance",
          "dynamic": false,
          "status": "active",
          "interfaces": {
            "Ethernet1": {
              "privatePromoted": false,
              "blocked": null
            },
            "Ethernet2": {
              "privatePromoted": false,
              "blocked": null
            },
            "Ethernet5": {
              "privatePromoted": false,
              "blocked": null
            }
          }
        },
        "20": {
          "name": "sales",
          "dynamic": false,
          "status": "active",
          "interfaces": {
            "Ethernet1": {
              "privatePromoted": false,
              "blocked": null
            },
            "Ethernet2": {
              "privatePromoted": false,
              "blocked": null
            }
          }
        },
        "30": {
          "name": "cctv",
          "dynamic": false,
          "status": "active",
          "interfaces": {
            "Ethernet1": {
              "privatePromoted": false,
              "blocked": null
            },
            "Ethernet2": {
              "privatePromoted": false,
              "blocked": null
            },
            "Ethernet6": {
              "privatePromoted": false,
              "blocked": null
            }
          }
        }
      },
      "sourceDetail": ""
    },
    "encoding": "json"
  }
]

Getting Raw Output

For whatever reason, you just want to get the raw output, you can set the encoding to text instead of JSON (default is JSON)

import pyeapi

connection = pyeapi.client.connect(
    transport='https',
    host='192.168.100.212',
    username='admin',
    password='admin',
    port=443
)

node = pyeapi.client.Node(connection)
response = node.enable('show vlan', encoding='text')

print(response[0]['result']['output'])
VLAN  Name                             Status    Ports
----- -------------------------------- --------- -------------------------------
1     default                          active    
10    finance                          active    Et1, Et2, Et5
20    sales                            active    Et1, Et2
30    cctv                             active    Et1, Et2, Et6
98    VLAN0098                         active    
100   VLAN_100                         active

Using the nodes.conf File

You can also use a nodes.conf file to define your devices, serving as an inventory file. Here's an example nodes.conf file.

[connection:access-01]
host: 192.168.100.212
transport: https
username: admin
password: admin

In this configuration file, we define a device called access-01 with its connection details such as the host, transport type, username, and password. The script below shows how to use nodes.conf to connect to the device.

import pyeapi
import json

pyeapi.load_config('nodes.conf')
node = pyeapi.connect_to('access-01')

response = node.enable(["sh vlan"])
print(json.dumps(response, indent=2))

In this script, we first load the nodes.conf file using pyeapi.load_config(). Then, we connect to the device using pyeapi.connect_to('access-01'). The rest of the script is similar to the previous example, sending a command ("show vlan") and pretty-printing the output.

Hiding Secrets

In the first example, we hard-coded the password directly into the script, which is not recommended at all, as it poses a security risk. In the updated script below, we use an environment variable instead.

import pyeapi
import json
import os

password = os.environ.get('PWD')

connection = pyeapi.client.connect(
    transport="https",
    host="192.168.100.212",
    username="admin",
    password=password,
    port=443
)

node = pyeapi.client.Node(connection)
response = node.enable(["show vlan"])
print(json.dumps(response, indent=2))

Here, we retrieve the password from an environment variable using os.environ.get('PWD'). This keeps sensitive information out of your script and reduces the risk of exposing passwords.

💡
I haven't found a way to hide the password from the nodes.conf file yet. If you know of a solution, please let me know in the comments.

PyeAPI Modules

We know we can use any show commands and get parsed output, which is great, but pyeapi also comes with predefined modules for managing configurations and retrieving specific outputs.

Get Interfaces

First, let's look at how to get specific outputs. You can find the available modules here.

import pyeapi
import json
import os

password = os.environ.get('PWD')

connection = pyeapi.client.connect(
    transport="https",
    host="192.168.100.212",
    username="admin",
    password=password,
    port=443
)

node = pyeapi.client.Node(connection)
interfaces = node.api('interfaces')
print(json.dumps(interfaces.getall(), indent=2))

In this script, we're using the api('interfaces') method to interact directly with the interfaces on the device. After establishing the connection, node.api('interfaces') provides an instance of the interfaces API.

The interfaces.getall() function then fetches details of all interfaces, giving us structured data to work with.

{
  "defaults": {
    "name": "defaults",
    "type": "generic",
    "shutdown": false,
    "description": null
  },
  "Ethernet1": {
    "name": "Ethernet1",
    "type": "ethernet",
    "shutdown": false,
    "description": "CCTV",
    "sflow": true,
    "flowcontrol_send": "off",
    "flowcontrol_receive": "off"
  },
  "Ethernet2": {
    "name": "Ethernet2",
    "type": "ethernet",
    "shutdown": false,
    "description": null,
    "sflow": true,
    "flowcontrol_send": "off",
    "flowcontrol_receive": "off"
  },
  "Ethernet5": {
    "name": "Ethernet5",
    "type": "ethernet",
    "shutdown": false,
    "description": null,
    "sflow": true,
    "flowcontrol_send": "off",
    "flowcontrol_receive": "off"
  },
  "Ethernet6": {
    "name": "Ethernet6",
    "type": "ethernet",
    "shutdown": false,
    "description": null,
    "sflow": true,
    "flowcontrol_send": "off",
    "flowcontrol_receive": "off"
  },
  "Management0": {
    "name": "Management0",
    "type": "generic",
    "shutdown": false,
    "description": null
  }
}

If you want to get information about IP addresses, you can use ipinterfaces instead. Here I'm targeting a different device that has L3 interfaces.

node = pyeapi.client.Node(connection)
interfaces = node.api('ipinterfaces')
print(json.dumps(interfaces.getall(), indent=2))
{
  "defaults": {
    "name": "defaults",
    "address": null,
    "mtu": null
  },
  "Management0": {
    "name": "Management0",
    "address": "192.168.100.210/24",
    "mtu": null
  },
  "Vlan10": {
    "name": "Vlan10",
    "address": "10.125.10.2/24",
    "mtu": null
  },
  "Vlan20": {
    "name": "Vlan20",
    "address": "10.125.20.2/24",
    "mtu": null
  },
  "Vlan30": {
    "name": "Vlan30",
    "address": "10.125.30.2/24",
    "mtu": null
  }
}

Manage VLANs

Now let's look at how to manage VLANs. Just like with interfaces, you can use the module vlans to manage VLANs. Here is an example for retrieving VLANs.

import pyeapi
import json
import os

password = os.environ.get('PWD')

connection = pyeapi.client.connect(
    transport="https",
    host="192.168.100.210",
    username="admin",
    password=password,
    port=443)

node = pyeapi.client.Node(connection)
vlans = node.api('vlans')

print(json.dumps(vlans.getall(), indent=2))

In the next script, we are creating a new VLAN with ID 100 and assigning it a name.

import pyeapi
import json
import os

password = os.environ.get('PWD')

connection = pyeapi.client.connect(
    transport="https",
    host="192.168.100.212",
    username="admin",
    password=password,
    port=443)

node = pyeapi.client.Node(connection)
vlans = node.api('vlans')

output = vlans.configure_vlan(100, ['name VLAN_100'])
print(output)

After establishing the connection, we use node.api('vlans') to get an instance of the VLANs API. Then, we use vlans.configure_vlan(100, ['name VLAN_100']) to create VLAN 100 and assign it the name VLAN_100.

The configure_vlan method allows us to specify the VLAN ID and configure attributes (like name) through a list. The output of the operation is then printed for confirmation (prints true)

SSL/TLS Errors

If you encounter any SSL errors similar to the following, One way to fix it is to create a custom SSL context and pass it during the connection.

pyeapi.eapilib.ConnectionError: Socket error during eAPI connection: [SSL: 
SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1007)
import ssl

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
context.set_ciphers('AES256-SHA:DHE-RSA-AES256-SHA:AES128-SHA:DHE-RSA-AES128-SHA')

connection = pyeapi.client.connect(
    transport='https',
    host='host',
    username='admin',
    password='admin',
    port=443,
    context=context
)

Closing Up

I hope you find this post useful. There are plenty of modules available to manage various parts of the configuration. Feel free to try them out, and let me know in the comments if you have any questions.