Saltar al contenido

Ejecuta comandos sobre ssh con Python

octubre 3, 2021
apple touch icon@2

Primero: me sorprende que nadie haya mencionado fabric todavía.

Segundo: exactamente para los requisitos que describe, he implementado un módulo de Python propio llamado jk_simpleexec. Su propósito: facilitar la ejecución de comandos.

Déjame explicarte un poco al respecto.

El problema de ‘ejecutar un comando localmente’

Mi módulo de Python jk_simpleexec proporciona una función llamada runCmd(..) que puede ejecutar un comando de shell (!) de forma local o remota. Esto es muy simple. A continuación, se muestra un ejemplo de ejecución local de un comando:

import jk_simpleexec

cmdResult = jk_simpleexec.runCmd(None, "cd / ; ls -la")

NOTA: Tenga en cuenta que los datos devueltos se recortan automáticamente de forma predeterminada para eliminar el exceso de líneas vacías de STDOUT y STDERR. (Por supuesto, este comportamiento se puede desactivar, pero para el propósito que tiene en mente exactamente ese comportamiento es lo que querrá).

El problema de ‘procesar el resultado’

Lo que recibirá es un objeto que contiene el código de retorno, STDOUT y STDERR. Por tanto, es muy fácil procesar el resultado.

Y esto es lo que desea hacer, ya que el comando que ejecuta puede existir y se inicia, pero puede fallar al hacer lo que se pretende que haga. En el caso más simple en el que no está interesado en STDOUT y STDERR, su código probablemente se verá así:

cmdResult.raiseExceptionOnError("Something went wrong!", bDumpStatusOnError=True)

Para fines de depuración, desea enviar el resultado a STDOUT en algún momento, por lo que puede hacer esto:

cmdResult.dump()

Si desea procesar STDOUT, también es simple. Ejemplo:

for line in cmdResult.stdOutLines:
    print(line)

El problema de ‘ejecutar un comando de forma remota’

Ahora, por supuesto, es posible que deseemos ejecutar este comando de forma remota en otro sistema. Para esto podemos usar la misma función runCmd(..) exactamente de la misma manera, pero necesitamos especificar un fabric objeto de conexión primero. Esto se puede hacer así:

from fabric import Connection

REMOTE_HOST = "myhost"
REMOTE_PORT = 22
REMOTE_LOGIN = "mylogin"
REMOTE_PASSWORD = "mypwd"
c = Connection(host=REMOTE_HOST, user=REMOTE_LOGIN, port=REMOTE_PORT, connect_kwargs={"password": REMOTE_PASSWORD})

cmdResult = jk_simpleexec.runCmd(c, "cd / ; ls -la")

# ... process the result stored in cmdResult ...

c.close()

Todo permanece exactamente igual, pero esta vez ejecutamos este comando en otro host. Este es destinado a: Quería tener una API uniforme donde no se requieran modificaciones en el software si en algún momento decide pasar del host local a otro host.

El problema de entrada de contraseña

Ahora, por supuesto, existe el problema de la contraseña. Algunos usuarios han mencionado esto anteriormente: es posible que deseemos pedirle una contraseña al usuario que ejecuta este código de Python.

Para este problema he creado un módulo propio hace bastante tiempo. jk_pwdinput. La diferencia con la entrada de contraseña normal es que jk_pwdinput generará algunas estrellas en lugar de simplemente no imprimir nada. Entonces, por cada carácter de contraseña que ingrese, verá una estrella. De esta manera, es más fácil para usted ingresar una contraseña.

Aquí está el código:

import jk_pwdinput

# ... define other 'constants' such as REMOTE_LOGIN, REMOTE_HOST ...

REMOTE_PASSWORD = jk_pwdinput.readpwd("Password for " + REMOTE_LOGIN + "@" + REMOTE_HOST + ": ")

(Para completar: Si readpwd(..) regresó None el usuario canceló la entrada de la contraseña con Ctrl + C. En un escenario del mundo real, es posible que desee actuar de manera adecuada).

Ejemplo completo

Aquí hay un ejemplo completo:

import jk_simpleexec
import jk_pwdinput
from fabric import Connection

REMOTE_HOST = "myhost"
REMOTE_PORT = 22
REMOTE_LOGIN = "mylogin"
REMOTE_PASSWORD = jk_pwdinput.readpwd("Password for " + REMOTE_LOGIN + "@" + REMOTE_HOST + ": ")
c = Connection(host=REMOTE_HOST, user=REMOTE_LOGIN, port=REMOTE_PORT, connect_kwargs={"password": REMOTE_PASSWORD})

cmdResult = jk_simpleexec.runCmd(
    c = c,
    command = "cd / ; ls -la"
)
cmdResult.raiseExceptionOnError("Something went wrong!", bDumpStatusOnError=True)

c.close()

Notas finales

Entonces tenemos el conjunto completo:

  • Ejecutando un comando,
  • ejecutar ese comando de forma remota a través de la misma API,
  • creando la conexión de una manera fácil y segura con la entrada de contraseña.

El código anterior resuelve el problema bastante bien para mí (y con suerte también para ti). Y todo es de código abierto: Fabric es Cláusula BSD-2, y mis propios módulos se proporcionan en Apache-2.

Módulos utilizados:

¡Feliz codificación! 😉

close