Automatizando atualização de aplicação com Git

Se você tem uma aplicação sendo versionada com Git, acredito que já tenha sentido a necessidade de automatizar o processo de atualização dessa aplicação, ao invés de ter que atualizar os arquivos manualmente ou então ter que logar no servidor via SSH para rodar um git pull toda vez que houver uma atualização.

E se assim que você fizesse a atualização local, desse um git push origin para atualizar o repositório remoto (no Github, por exemplo) e depois desse apenas um git push servidor para atualizar a aplicação em produção, seria muito mais simples certo?

Neste artigo, pretendo mostrar uma forma para automatizar o processo de atualização de uma aplicação, utilizando Git Hooks. Os exemplos mostrados neste artigo foram testados no Ubuntu 16.04.

Animação 1 – Demonstração do procedimento de atualização

Pré-requisitos

Antes de iniciar, são necessários alguns pré-requisitos no servidor de produção da sua aplicação.

  • Acesso via SSH;
  • Git instalado.

Criando repositório local

Primeiramente, vamos criar um repositório local em nosso computador apenas para servir de teste durante o artigo. Para isso, criaremos uma pasta chamada deploy-automatico e adicionaremos um arquivo.

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Deploy automático</title>
</head>
<body>
    <h1>Deploy automático funcionando!</h1>
</body>
</html>

Agora vamos abrir o terminal na pasta da aplicação que foi criada, iniciar um repositório Git e dar o commit inicial.

# Iniciando repositório
git init

# Adicionando arquivo e commitando
git add index.html
git commit -m "Commit inicial"
Imagem 1 – Inicializando repositório local

Configurando o servidor

Agora que já temos uma aplicação para testar, vamos acessar o nosso servidor via SSH.

ssh rafael@rafaelcouto.com.br

Criando pasta do repositório

Na pasta do meu usuário, vou criar uma outra pasta que será a pasta base do nosso repositório.

mkdir /home/rafael/deploy-automatico

Vamos então entrar nesta pasta e iniciar um repositório Git com a opção --bare.

# Entrando na pasta
cd /home/rafael/deploy-automatico

# Iniciando repositório bare
git init --bare
Imagem 2 – Criando repositório base

Perceba na Imagem 2 que será criado uma estrutura semelhante ao que temos na pasta .git do nosso repositório local. Um repositório bare nada mais é que um repositório sem diretório de trabalho, ou seja, é utilizado apenas para compartilhamento e não possui diretamente o código fonte da nossa aplicação.

Imagem 3 – Diferença entre repositório bare e non-bare
Fonte: <https://www.atlassian.com/git/tutorials/setting-up-a-repository/git-init>

Portanto, um repositório bare é um repositório central que nos permite atualizá-lo através do git push. E no caso, o nosso repositório local é um repositório non-bare.

Criando hook

Dentro da pasta do repositório que criamos temos a pasta hooks que nos permite definir gatilhos para determinados eventos. O que queremos é que, ao receber um push, nós atualizemos o código fonte da nossa aplicação, para isso vamos criar um post-receive hook.

# Entrando na pasta
cd /home/rafael/deploy-automatico/hooks

# Criando arquivo do hook
nano post-receive

Estou utilizando o nano como editor, porém você pode utilizar o vim ou qualquer editor de sua preferência. Vamos então colocar o conteúdo no arquivo, depois basta pressionar CTRL+X e depois Y para salvar.

#!/bin/bash

# Armazenando caminho do código fonte em variável
worktree=/var/www/deploy-automatico

# Atualizando código fonte
git --work-tree="$worktree" checkout -f

Utilizamos o comando git checkout com a opção --work-tree, que nos permite indicar a pasta do nosso código fonte. No meu caso, o código fonte da aplicação está em /var/www/deploy-automatico que é pasta raiz de um subdominio que criei para essa aplicação. Lembrando que a pasta do código fonte deve ter permissão para o seu usuário criar arquivos e deve estar vazia para o primeiro push.

Por fim, vamos dar permissão de execução para nosso hook.

chmod +x post-receive

Pronto, agora quando recebermos um push, as alterações serão sincronizadas com a pasta do código fonte, atualizando assim a nossa aplicação. O próximo passo agora é configurar nosso repositório local.

Configurando repositório local

Agora na aplicação de teste que criamos, vamos adicionar o endereço remoto para nos permitir executar um push para nosso servidor. Para isso utilizamos o git remote add, que possui a seguinte sintaxe.

git remote add <nome> <usuario>@<endereco-remoto>:<caminho-repositorio>

Portanto, o comando no meu caso ficou da seguinte forma.

git remote add servidor rafael@rafaelcouto.com.br:deploy-automatico

O nome é apenas o identificador que você vai utilizar ao executar o push, no caso eu deixei servidor. O usuário e o endereço remoto é o mesmo que se utiliza para o acesso SSH. Como a pasta padrão do usuário rafael é /home/rafael, eu posso informar o caminho relativo do repositório, entretanto, você também poderia utilizar o caminho completo.

git remote add servidor rafael@rafaelcouto.com.br:/home/rafael/deploy-automatico

Depois podemos executar um git remote -v apenas para verificar se o endereço remoto foi realmente adicionado.

Imagem 4 – Adicionado endereço remoto

Vamos então fazermos um push para o servidor do branch master. Para isso fazemos da mesma forma quando, por exemplo, queremos atualizar o repositório no Github, apenas substituindo o origin por servidor.

git push servidor master
Imagem 5 – Fazendo primeiro push

Pronto, agora basta acessarmos o endereço da aplicação para verificar se tudo funcionou.

Imagem 6 – Aplicação funcionando após primeiro push

Atualizando aplicação

Após fazer o primeiro push, vamos atualizar nosso arquivo index.html apenas para testar se o processo de atualização está funcionando.

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Deploy automático</title>
</head>
<body>
    <h1>Deploy automático funcionando!</h1>

    <p>
        Acesse o artigo <a href="https://rafaelcouto.com.br/automatizando-atualizacao-de-aplicacao-com-git">clicando aqui</a>.
    </p>
</body>
</html>

Vamos então fazer o commit da nossa alteração e o push para o servidor.

# Adicionando arquivo e commitando
git add index.html
git commit -m "Atualizado index.html"

# Atualizando servidor
git push servidor master
Imagem 7 – Atualizando aplicação

Por fim, vamos acessar o endereço da aplicação para verificar se tudo funcionou.

Imagem 8 – Aplicação funcionando após atualização

Automatização completa

Agora que já temos o processo de atualização automatizado, podemos utilizar um pouco de Shell Script e automatizar ainda mais o processo. Para evento de post-receive, o Git disponibiliza alguns argumentos que são muito úteis.

#!/bin/bash

# Armazenando argumentos em variáveis
read oldrev newrev ref

# Armazenando caminho do código fonte em variável
worktree=/var/www/deploy-automatico

# Atualizando código fonte
git --work-tree="$worktree" checkout -f

# Exibindo valores das variáveis
echo "Old revision: $oldrev"
echo "New revision: $newrev"
echo "Reference name: $ref"
Imagem 9 – Exibindo valores dos argumentos

Com essas variáveis podemos, por exemplo, comparar se houve alteração em algum arquivo.

if git --work-tree="$worktree" diff --name-only $oldrev $newrev | grep "^index\.html$" -q; then
    echo "Arquivo index.html alterado"
fi

Através do git diff conseguimos verificar os arquivos que foram alterados entre dois commits, e utilizamos a opção --name-only, pois queremos apenas os nomes dos arquivos alterados. Tendo o nomes dos arquivos, utilizamos o comando grep para verificar se o arquivo buscado está entre eles, se estiver será exibido a mensagem. Com isso podemos fazer várias coisas, vejamos alguns exemplos.

1. Composer

Em uma aplicação PHP é muito comum utilizarmos o Composer, portanto quando você atualizar a aplicação é interessante rodar um composer update caso alguma biblioteca tenha sido adicionada ou removida.

if git --work-tree="$worktree" diff --name-only $oldrev $newrev | grep "^composer\.json$" -q; then
    echo "Atualizando bibliotecas"
    composer --working-dir=$worktree update --no-interaction
fi

Verificamos se o arquivo composer.json foi alterado, então rodamos o composer update utilizando a opção --working-dir para indicar o caminho da aplicação e a opção --no-interaction para não fazer perguntas ao usuário já que é um processo automático.

2. Laravel

Se você possuir uma aplicação Laravel, provavelmente vai querer colocar o sistema em modo de manutenção enquanto está atualizando, além de rodar as migrations.

echo "Iniciando modo de manutenção"
php $worktree/artisan down

echo "Atualizando código fonte"
git --work-tree="$worktree" checkout -f

echo "Rodando migrations";
php $worktree/artisan migrate --force

echo "Finalizando modo de manutenção"
php $worktree/artisan up

Utilizamos o php para chamar o artisan na pasta da nossa aplicação e então podemos utilizar os comandos disponíveis, como por exemplo, o up, down e migrate. O migrate utilizamos com o --force para evitar confirmação que está em ambiente de produção.

3. Gulp

Caso sua aplicação possua alguma tarefa do Gulp, você pode executá-la também. Por exemplo, caso algum arquivo javascript tenha sido alterado e você quer rodar uma tarefa de build.

if git --work-tree="$worktree" diff --name-only $oldrev $newrev | grep "^public\/assets\/.*\?\.js$" -q; then
    echo "Refazendo build da aplicação"
    gulp build --cwd "$worktree"
fi

Nesse caso, se qualquer arquivo javascript (.js) que esteja na pasta public/assets tiver sido alterado, então chamamos a tarefa build registrada no Gulp.

4. Outros

Enfim, com Shell Script o céu é o limite! Portanto, você pode rodar qualquer comando disponível no servidor para automatizar ainda mais o processo de atualização, como por exemplo, algum comando para fazer backup do banco de dados, limpar algum cache, compilar alguma coisa, etc.

Conclusão

Neste artigo vimos uma forma de automatizar o processo de atualização de uma aplicação em produção de uma forma relativamente simples.

Vale ressaltar que a aplicação em produção será sempre sincronizada com a branch master, ou seja, caso haja alguma alteração incompleta ou algum bug, será liberado em produção. Entretanto, se você aplica o conceito de Git Flow em suas aplicações, deveria diminuir as chances de problemas.

E vale lembrar também que você não deve utilizar esse método para manter o versionamento, continue utilizando o Github, Bitbucket ou qualquer outro serviço para gerenciar as versões da sua aplicação.

Enfim, espero que este artigo tenha sido útil de alguma forma. Até a próxima.

Código fonte disponível em: https://github.com/rafaelcouto/automatizando-atualizacao-de-aplicacao-com-git

Referências