Trabalhando com a CLI do Zimbra usando o Pexpect

Um dos meus maiores desgostos na vida é ter que trabalhar com scripts de automação de atividades com o zmprov, o cliente de “Interface de Linha de Comando” (CLI) fornecido pela Synacor. Cada execução do zmprov leva em torno de 20 segundos a 1 minuto para executar e devolver algum retorno para mim. Agora imagine eu repetir isso n vezes, sendo n igual ao número de vezes que eu quero repetir a execução. Eu acabo com um script que leva em torno de dias para executar o que eu preciso. Esse post tem o intuito de explicar como eu consegui resolver meu problema do zmprov usando o Python para agilizar a atividade, mais especificadamente o módulo pexpect.

 

Problema do Zmprov

 

A um tempo atrás eu tive que atender um cliente que necessitava que fosse desenvolvido uma nova interface para o Zimbra de autenticação através do método preauth, aonde é utilizado uma chave do domínio para gerar um token, e posteriormente entregue ao Zimbra para liberar o acesso ao Webmail para o cliente. O real problema é que esse cliente é um fornecedor de hospedagem (que nem a LocaWeb e a Hostgator), então existe a expectativa de crescimento no número de domínios que eles irão atender futuramente. Eu poderia simplesmente seguir o procedimento de cabo a rabo do Alisson Machado (link no final do post) e desenvolver um script de autenticação usando o PHP (que era a linguagem que eu precisava usar para desenvolver) com a chave de pré autenticação do domínio setado dentro de uma constante, conforme abaixo:

define('ZMTOKEN','4e2816f16c44fab20ecdee39fb850c3b0bb54d03f1d8e073aaea376a4f407f0c')

Porém, essa chave é única para cada domínio, e como existe a expectativa de crescimento do número de domínios que eles irão atender, eis que não terá como eu colocar esse valor dentro de uma constante dentro do arquivo.php. Eu precisei criar uma API em Flask que se comunicasse com a CLI do Zimbra para consultar esses valores, e então retornar a chave correta para o servidor. Porém eis que tenho mais um problema: a CLI do Zimbra é lenta, muito lenta mesmo, e consome muito tempo de CPU do servidor para cada execução dela. Foi necessário então eu desenvolver uma forma de cachear o valor dentro de uma base SQLite, porém ainda sim tenho o problema de lentidão de consultar previamente todas essas chaves antes de salvar elas em cache na minha base de dados.

Essa demora do zmprov é ocasionada pela inicialização dele. Ao digitar “zmprov” somente, levará algum tempo até retornar um shell para que possa digitar os comandos. O ponto interessante é que uma vez que você esteja nessa console, todos os comandos serão executados rapidamente, como por exemplo o que eu preciso: gd <dominio> zimbraPreAuthKey. Isso se tornou a solução que eu precisava para resolver o problema da demora da execução dos comandos, mas a biblioteca subprocess do Python não trabalha da maneira que eu preciso com CLIs. O subprocess possui diversos métodos, entre eles os que mais gosto de usar que são o check_output (que permite uma única execução), e o Popen (que permite multiplas execuções, porém é necessário que o processo seja encerrado antes de ter acesso a sua saída).

Trabalhando com o Pexpect

 

Em minhas buscas acabei me deparando com uma biblioteca do Python chamada Pexpect, desenvolvida por Noah Spurrier, que permite eu controlar o fluxo dos dados, desde do que entra na CLI até o que eu quero que seja retornado por ela. O ponto mais interessante é que essa biblioteca já se encontra instalada em qualquer computador que possua o Python instalado. Para utilizar o pexpect, importe-o no seu script, e então crie uma instância da classe spawn, conforme abaixo. Lembre-se de passar o comando de CLI que deseja carregar. No meu caso foi o “/opt/zimbra/bin/zmprov”.

import pexpect
child = pexpect.spawn("/opt/zimbra/bin/zmprov")

Antes de passarmos qualquer coisa para o pexpect, primeiro devemos informar qual é o retorno que ele deve esperar do comando anterior. Como o próprio nome diz (pexpect – python expect): o pexpect espera que uma ação seja executada para que ele tome alguma reação. Caso não seja informado o que ele deve esperar, nada será executado ou retornado. Para informarmos o que ele deve esperar usamos o método “expect” conforme no exemplo abaixo:

child.expect("zmprov>")

Ao criarmos uma instância de zmprov, nosso shell é trocado para o do Zimbra e então nosso cursor fica parado numa linha aonde o único conteúdo é justamente “zmprov>”. É exatamente isso que eu devo informar para o pexpect, dessa forma ele sabe o que deve esperar do comando anterior. Agora, através do método “sendline”, informe o comando que deseja executar na CLI.

child.sendline("gd <dominio> zimbraPreAuthKey")

Após a execução, eu devo novamente usar o método expect, informando que o módulo deve esperar nesse momento a mensagem “zmprov>”, e então poderemos ter acesso ao resultado do comando através da variável before, conforme abaixo. No meu caso, por conta do comando anterior trazer o resultado dividido em linhas, eu preferi usar o método splitlines para dividir essa string em uma lista.

rst = child.before.splitlines()

Caso queira executar outro comando basta executar novamente o método sendline, depois expect, e por ultimo acessar a variável before, para então colher a resposta. Para encerrar o processo, basta chamar o método .close() e a CLI do Zimbra será encerrada.

child.close()

Com isso consegui contornar meu problema do zmprov ser extremamente lento, agilizando meu script de 10 minutos para 20 segundos no melhor tempo com 100 domínios. Abaixo segue o trecho do código para vocês compreenderem como ele funciona na prática:

from pexpect import spawn
child = spawn(self.zmprov)
child.expect("prov>")
for i in lstdomain:
    child.sendline("gd {0} zimbraPreAuthKey".format(i))
    child.expect("prov>")
    rst = child.before.splitlines()
child.close()

Conclusão

 

Para esse post eu utilizei a documentação oficial do pexpect, na qual segue abaixo o link, além disso também estou incluindo o link sobre o post do Alisson Machado que eu havia comentado sobre o PreAuth do Zimbra com Python. Posteriormente pretendo escrever um post explicando como funciona o sistema de forma detalhada.

  • http://alissonmachado.com.br/python-zimbra-preauth/
  • https://pexpect.readthedocs.io/en/stable/

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *