Flow control with pexpect

Using pexpect is pretty straightforward, but what about the situations, when we don’t know in what state the device, that we’re interacting with is? In such a scenario, we have to implement logic in our script that could handle different machine states. In this article, we will go through a script, that handles logging into the Cisco router via the console session.

Workflow overview

Let’s take a look at our today’s workflow.


User Access Verification

Username: admin
Password:

Router#


We’re establishing a telnet connection with a console server, which gives us access to the Cisco router via serial connection. After that, we’re logging in with a username and password to our device.

Establishing connection

The connection part is similar to the one from the previous article.

import pexpect

username = "admin"
password = "admin"
prompt = "Router#"

child = pexpect.spawn("telnet 10.0.1.3 2233")
child.sendline("\r")

The main difference is that we’re using a telnet connection instead of ssh. Right after the creation of pexpect object, we’re sending a special character to the console. The effect of this sendline is similar to the user pressing enter button on the keyboard to refresh the console. By pressing it, we’re getting a response from the router, which is necessary for us to check the state of the machine. Based on the response, we can implement logic.

Scenario

While refreshing the console window, I recall three states, that can occur:

  1. Router will prompt us for the username
  2. Router will prompt us for a password – it means that someone earlier entered a username
  3. Router will display its prompt – a user is already logged in

That’s our today’s scenario. We will write a code that can handle such conditions.

Conditionals

We will use an already known pexpect method – expect. The difference is, instead of passing a string as an argument, we will pass a list of expected states. This time, however, we need to check what expect function is returning, because it will return an index indicating which list element was matched by the expect function.

index = child.expect([prompt, "Username:", "Password:"])

Let’s go through the list of elements to see what will happen if expect will match them:

  1. Prompt – it will return 0(first list index), and that’s the state we want to achieve
  2. “Username:” – it will return 1, receiving this we know that we need to send a username and password to log in
  3. “Password:” – it will return 2, after that, we need to send just the password

Based on that we can write an if-else structure with three scenarios – one for each response, but there is a better way to handle it.

We will implement a while loop.

index = 99
while index != 0:
    index = child.expect([prompt, "Username:", "Password:"])
    if index == 0:
        print("Found prompt!")
    elif index == 1:
        print(f"Found Username in the console, sending {username}")
        child.sendline(username)
    elif index == 2:
        print(f"Found Password in the console, sending {password}")
        child.sendline(password)

We’re starting with creating an index variable with 99 assigned to it. I used 99, because I’m sure that expect method is not going to assign this number in the first call. Then we’re entering the while loop. From now, we won’t break from it till finding a router prompt.

Every scenario described before is handled differently. When we’re finding a prompt, the information is printed on the console, and we’re basically doing nothing because the while loop will end right after it. For the “Username:” case, we’re using sendline to send the username to the router. In the next loop iteration, the router will ask us for the password, which will be sent by another sendline statement.

This approach besides code optimization has one huge advantage. It’s easy to extend. By adding expected states, we can add logic based on them, so this code snipped could end up as a full-blown function that finds a router prompt in any circumstances.

The complete script is presented below.

import pexpect

username = "admin"
password = "admin"
prompt = "Router#"

child = pexpect.spawn("telnet 10.0.1.3 2233")
child.sendline("\r")

index = 99
while index != 0:
    index = child.expect([prompt, "Username:", "Password:"])
    if index == 0:
        print("Found prompt!")
    elif index == 1:
        print(f"Found Username in the console, sending {username}")
        child.sendline(username)
    elif index == 2:
        print(f"Found Password in the console, sending {password}")
        child.sendline(password)

You can also access the code on my GitLab page

The console output looks like this.

Found Username in the console, sending admin
Found Password in the console, sending admin
Found prompt!

Summary

That’s for the basics of flow control for the pexpect library. Using conditionals is useful not only for finding a router prompt, but could be necessary for proper command output gathering. More on that in the next articles.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *