0

I've been trying to write an expect script which has one method to open an ssh connection and this method can be called by other methods so that they can send additional commands for execution. What I already have is -

#!/usr/bin/expect

set timeout -1
#log_user 0
set username [lindex ${argv} 0]
set node_address [lindex ${argv} 1]
set password [lindex ${argv} 2]
set prompt ".*\[>#:$%\] \?\$"
set password_prompt ".*: \?\$"
set install_dir "/usr/share/elasticsearch"
set conf_dir "/etc/elasticsearch"
set plugin_manager "${install_dir}/bin/elasticsearch-plugin"
set es_config_file "${conf_dir}/elasticsearch.yml"

puts "Attempting login to ${node_address}"
spawn ssh ${username}@${node_address}
expect -re ${password_prompt} {send -- "${password}\n"}
expect -re ${prompt} {send -- "sudo su - kompuser\n"}
expect -re ${prompt}
puts "Logged in to the node ${node_address} successfully"}

expect -re ${prompt}
send -- "curl -XGET 'localhost:9200/_cat/nodes?v&h=ip,node.role'\n"
expect -re ${prompt}
send_user $expect_out(buffer)

I have other scripts which try to login to a host using the similar code and have lots of code duplication. I want to avoid the code duplication by writing a single method, let's call it login and invoke it before sending any command to the remote server, what I want is -

#!/usr/bin/expect

set timeout -1
#log_user 0
set username [lindex ${argv} 0]
set node_address [lindex ${argv} 1]
set password [lindex ${argv} 2]
set prompt ".*\[>#:$%\] \?\$"
set password_prompt ".*: \?\$"
set install_dir "/usr/share/elasticsearch"
set conf_dir "/etc/elasticsearch"
set plugin_manager "${install_dir}/bin/elasticsearch-plugin"
set es_config_file "${conf_dir}/elasticsearch.yml"

proc login {username node_address password prompt password_prompt} {
    puts "Attempting login to ${node_address}"
    spawn ssh ${username}@${node_address}
    expect -re ${password_prompt} {send -- "${password}\n"}
    expect -re ${prompt} {send -- "sudo su - kompuser\n"}
    puts "Logged in to the node ${node_address} successfully"}

proc get_node_info {username node_address password prompt password_prompt} {
    login ${username} ${node_address} ${password} ${prompt} ${password_prompt}
    expect -re ${prompt}
    send -- "curl -XGET 'localhost:9200/_cat/nodes?v&h=ip,node.role'\n"
    expect -re ${prompt}}

proc restart_node {username node_address password prompt password_prompt} {
    login ${username} ${node_address} ${password} ${prompt} ${password_prompt}
    expect -re ${prompt} {send -- "sudo systemctl daemon-reload\n"}
    expect -re ${prompt} {send -- "sudo systemctl start elasticsearch.service\n"}
    expect -re ${prompt}
}

get_node_info ${username} ${node_address} ${password} ${prompt} ${password_prompt}
restart_node ${username} ${node_address} ${password} ${prompt} ${password_prompt}

The issue is that call to login logs in successfully but expecting for the prompt in the caller hangs - Following are the last few lines of the logs on enabling the debug logs -

send: sending "sudo su - kompuser\n" to { exp4 }
Logged in to the node 10.109.14.73 successfully
Gate keeper glob pattern for '.*[>#:$%] ?$' is ''. Not usable, disabling the performance booster.

expect: does "" (spawn_id exp0) match regular expression ".*[>#:$%] ?$"? (No Gate, RE only) gate=yes re=no

Any help will be much appreciated. Thanks

7
  • But... why not ssh-copy-id and just use ssh? Looks like you should interest yourself in some automation tools, like ansible. Commented Jan 24, 2021 at 15:10
  • I have to switch the user after logging in with the current user's credentials. Just ssh will again require me to enter the password while switching the user. Sorry, if I missed something which I may not be aware of, but the suggested approach doesn't work. Commented Jan 24, 2021 at 15:34
  • Why not just ssh into the destination user? Add NOPASSWD to ssh config? Commented Jan 24, 2021 at 15:38
  • 1
    in a large deployment one would use a tool for that, ansible, puppet, chef, etc. Managing such thing manually is just pain and you're just reinventing the wheel - you're basically writing ansible become module, that does just that - passes a password to sudo to "become" a user. Commented Jan 24, 2021 at 16:00
  • 1
    Ooops, you are right..I should've looked for a better tool/framework before starting out on this and ansible surely fits in for the task. Thank you so much. This is the first time working on automation, so wasn't aware of these frameworks. Commented Jan 24, 2021 at 16:06

1 Answer 1

2

When you run spawn inside a proc, the variable spawn_id is created in the current stack frame, making it a local variable unless special measures are taken.

With that knowledge, the solution is simple: Declare spawn_id as a global before running the spawn command:

proc login {username node_address password prompt password_prompt} {
    global spawn_id
    puts "Attempting login to ${node_address}"
    spawn ssh ${username}@${node_address}
    expect -re ${password_prompt} {send -- "${password}\n"}
    expect -re ${prompt} {send -- "sudo su - kompuser\n"}
    puts "Logged in to the node ${node_address} successfully"
}

I believe you don't need to do that in the other procs. But for consistency, I would.

Sign up to request clarification or add additional context in comments.

3 Comments

Another question is - let's say I define the above method in an expect script and try to call it in a bash script, how can I do that? Tried some of the suggested approach but don't seem to get it working.
@yabhishek That creates a command (in Tcl, procedures are a kind of command) that you can call from the expect script. The bash script still has to call the expect script; the login command created above does not change that at all.
@DonalFellows Let's say that we define this procedure in a file called file.exp which is executable and now we want to call this procedure in a bash script named file2.sh. How can we do that? I tried everything I know, but nothing seems to work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.