Uma tarefa muito comum dos administradores de sistemas
Linux, é executar o mesmo comando em vários servidores distintos. Isso, com o objetivo de aplicar um patchs de segurança, instalar um novo pacote, efetuar alguma configuração e até mesmo padronizar configurações.
Para isso, utilizamos ferramentas como:
- Puppet
- Ansible
- Chef
- Fabric
E entre outras, essas são as mais conhecidas.
Mas é possível também, fazer essas configurações através do
Python. Existe um módulo chamado
paramiko, que foi criado justamente para fazer conexões via
SSH. Então, neste artigo vou mostrar a vocês como usar este módulo.
O primeiro passo, é instalar o
paramiko:
# pip install paramiko
Agora, segue o script completo:
#!/usr/bin/python
from paramiko import SSHClient
import paramiko
class SSH:
def __init__(self):
self.ssh = SSHClient()
self.ssh.load_system_host_keys()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.ssh.connect(hostname='127.0.0.1',username='root',password='SENHA_DE_ROOT')
def exec_cmd(self,cmd):
stdin,stdout,stderr = self.ssh.exec_command(cmd)
if stderr.channel.recv_exit_status() != 0:
print stderr.read()
else:
print stdout.read()
if __name__ == '__main__':
ssh = SSH()
ssh.exec_cmd("apt-get update")
Análise
Vamos entender o que faz esse script.
A primeira coisa a entender, é que foi criada uma classe chamada SSH, assim é possível facilitar algumas coisas, pois no método construtor, definido por "def __init__(self):", tem uma sequência de instruções para efetuar a conexão com um determinador servidor.
self.ssh = SSHClient()
Essa linha faz a instância da classe SSHClient, que foi importada do módulo paramiko logo no topo do script.
from paramiko import SSHClient
import paramiko
Na sequência do construtor, foi definida a instrução:
self.ssh.load_system_host_keys()
Essa instrução define que o paramiko vai ler todas as chaves cadastrados no arquivo "~/.ssh/known_hosts", assim, evitamos ter que ficar dando um "yes" ou "no" na hora de conectar em um servidor via SSH.
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
Nessa linha, é definido o que fazer quando a chave de um servidor não é encontrada no "~/.ssh/known_hosts", então, foi definido o método "paramiko.AutoAddPolicy()", assim quando um servidor for acessado pela primeira vez o paramiko, automaticamente, vai aceitar a chave desse servidor e cadastrar no arquivo "known_hosts".
self.ssh.connect(hostname='127.0.0.1',username='root',password='SENHA_DE_ROOT')
Nessa linha, definimos o servidor em que vamos conectar, o usuário e a senha, caso o seu acesso seja feito através de chaves, é possível omitir o username e o password, deixando somente a chave hostname com o IP do servidor que você quer conectar.
Por que todas essas instruções foram colocadas no método construtor? Dessa maneira, assim que for instanciado um objeto dessa classe SSH, a conexão já é feita automaticamente, então o método "exec_cmd" pode executar os comandos diretamente, sem a necessidade de ficar efetuando a conexão antes de executar qualquer comando.
Agora vamos analisar o método "exec_cmd".
O método "exec_cmd" tem apenas um parâmetro obrigatório, que é a variável "cmd", ela deve receber uma string com o comando que deve ser executado via SSH no servidor.
stdin,stdout,stderr = self.ssh.exec_command(cmd)
A linha acima usa o próprio atributo ssh da classe SSH, que contém uma instância do SSHClient do paramiko que já está conectada ao servidor. Tudo isso foi realizado no construtor dessa classe, essa instância possui um método chamado "exec_command" que recebe uma string como parâmetro, que será o comando executado no servidor.
Quando o comando é executado, esse método retorna uma tupla com 3 valores:
- Standard Input (Entrada padrão, normalmente uma entrada do teclado)
- Standard Output (Saída padrão, o que aparece na tela)
- Stander Error (Saída de Error, mensagem de erro mostrada na tela)
Todos foram abreviados como stdin, sdout, sdterr.
if stderr.channel.recv_exit_status() != 0:
print stderr.read()
else:
Depois de recebidos os valores retornados pelo "exec_command", precisamos saber se o comando deu erro ou não, para isso foi feito esse
if.
Na instrução "stderr.channel.recv_exit_status()", é verificado se o valor retornado da saída de erro é diferente de 0, se esse valor for diferente de zero, significa que um erro aconteceu. Por exemplo:
- erro 127 (Command not found)
- erro 1 (Erro ao executar o comando, pode ser um parâmetro invalido por exemplo)
Esses são os erros mais comuns.
Então, se for retornado um erro, a condição entra no primeiro bloco de instruções fazendo um print do erro do comando, caso contrário a saída padrão do comando será retornada na tela.
if __name__ == '__main__':
ssh = SSH()
ssh.exec_cmd("apt-get update")
Essas ultimas instruções são para testar o nosso script, a linha "if __name__ == '__main__':", diz que o bloco abaixo só será executado se o script for executado via linha de comando, caso você faça um import desse arquivo, essas instruções não serão executadas.
Na sequência, é instanciada a nossa classe SSH dentro da variável ssh, que acaba se tornando um objeto. Nesse momento, é feita a conexão com o servidor, uma vez que tudo foi definido no construtor da classe, então, logo abaixo em "ssh.exec_cmd("apt-get update")", é enviado o comando
apt-get update para atualizar a base de dados do
apt-get no servidor que quisermos, o script irá demorar um pouco e se o comando for executado com sucesso, na sua tela irá aparecer o resultado do comando executado.
Mais tutoriais estão em meu blog pessoal:
Obrigado \o