Entrega contínua de blogs Hugo com GitHub Actions

O Hugo é hoje um dos melhores e mais populares geradores de sites estáticos. Já o GitHub Pages oferece um excelente serviço para distribuir gratuitamente conteúdos estáticos, o que é perfeito para blogs. Logo, é natural que as duas ferramentas sejam comumente usadas em conjunto.

A partir do momento que o blog estiver todo configurado, com todos os templates customizados da forma como queremos, o dia-a-dia do blog passa a revolver em torno da criação de conteúdo.

Existem várias formas de trabalhar, mas qualquer workflow escolhido terá um mínimo de passos comuns:

  • Criar um novo conteúdo (como posts, artigos, imagens, etc)
  • Rodar o comando hugo com alguma parametrização para gerar o conteúdo estático atualizado
  • Publicar o novo conteúdo estático no repositório <nomeusuario>.github.io

O restante deste artigo vai tratar de configurar um workflow usando GitHub Actions para automatizar o processo de gerar conteúdo estático e publicá-lo. Ao final, teremos um processo de entrega contínua para nosso blog.

Nosso esquema será esse: Commit na master -> Workflow GitHub Actions -> Blog github.io atualizado

A única ação necessária será um commit no branch master contendo as alterações desejadas. Estou usando este fluxo de trabalho e o considero bem fácil e prático.

O tutorial abaixo assume que você está usando dois repositórios:

  1. Repositório com o template do Hugo e o conteúdo
  2. Repositório <nomeusuario>.github.io

E tudo o que você precisa para implementá-lo é seguir os passos descritos abaixo.

Configuração do workflow de CI/CD com GitHub Actions

Até uns dias atrás, eu nunca tinha trabalho com as ferramentas de CI/CD do GitHub. Mas o GitHub Actions se mostrou bastante fácil de aprender, especialmente para quem já tem alguma experiência com ferramentas de CI/CD como o Circle CI ou Travis.

O GitHub Actions chama os pipelines de workflows. Para criar um novo workflow, primeiro você deve criar, na raiz do repositório do seu blog (onde fica seu template), a seguinte estrutura de diretórios:

.github/workflows

Dentro você terá de adicionar um arquivo de workflow. Ele pode ter qualquer nome, e deve terminar com a extensão .yml ou .yaml.

Abaixo, podemos ver o workflow que estou usando no meu blog atualmente, já comentado. Você também pode baixar ele aqui.

name: Deploy para <nomeusuario>.github.io
on:
  push:
    branches:
      - master # somente commits no branch master geram deploys
  # Também pode rodar um deploy automaticamente todos os dias
  # para publicar posts agendados (com datas futuras)
  schedule:
    - cron: '0 8 * * *' # 08:00 am UTC

jobs:
  # O job "build" é responsável por baixar o template, o hugo e
  # compilar o site estático
  build:
    name: Gerar conteúdo estático
    runs-on: ubuntu-latest
    steps:
        # Primeiro, fazemos checkout do repositório
      - uses: actions/checkout@master
        # Depois, usamos uma action para clonar o repositório do tema
        # Substitua o endereço e o diretório por um de sua preferência
      - name: Atualizar template
        uses: "srt32/git-actions@v0.0.3"
        with:
          args: "git clone https://github.com/htr3n/hyde-hyde.git themes/hyde-hyde"
        # Permite instalar qualquer versão do hugo que você precisar
      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: '0.69.0' # se quiser a última, pode usar 'latest'
          extended: true # permite compilar SCSS e SASS
      - name: Build
        run: hugo # gera os arquivos estáticos
        # Esta action faz upload do diretório /public como artefato
        # do job "build"
      - name: Upload dos artefatos
        uses: actions/upload-artifact@v1
        with:
          name: public
          path: ./public
  publish:
    name: Publish to <nomeusuario>.github.io
    runs-on: ubuntu-latest
    needs: build # o job "publish" só roda se deu tudo certo durante o "build"
    steps:
        # Primeiro fazemos checkout do nosso repositório github.io
        # no diretório static-site
      - uses: actions/checkout@v2
        with:
          repository: '<nomeusuario>/<nomeusuario>.github.io'
          # token com as permissões necessárias
          token: ${{ secrets.CD_BLOG_TOKEN }}
          path: static-site
        # Fazemos download dos artefatos do job "build" para o
        # diretório /public
      - name: Download dos artefatos
        uses: actions/download-artifact@v1
        with:
          name: public
        # Copiamos os novos arquivos estáticos em /public para /static-site
      - name: Aplicar atualização
        shell: bash
        run: |
                    cp -r ./public/* ./static-site
        # Adicionamos as alterações e geramos um commit
      - name: Commit files
        run: |
          cd static-site
          git config --local user.email "<email-valido>"
          git config --local user.name "GitHub Action - Deploy"
          git add .
          git commit -m "Add changes" -a          
        # Por último, precisamos fazer um push para o repositório github.io
        # de destino
      - name: Push changes
        uses: ad-m/github-push-action@master
        with:
          repository: '<nomeusuario>/<nomeusuario>.github.io'
          github_token: ${{ secrets.CD_BLOG_TOKEN }}
          directory: static-site

Sera necessário subsituir algumas variáveis no template, como o <nomeusuario>, o <email-valido>, e o template utilizado.

Além disso, você deve ter notado que em dois momentos precisamos de um token do GitHub, como nesta linha: github_token: ${{ secrets.CD_BLOG_TOKEN }}. É bem fácil gerar e cadastrar este token.

Settings/Developer settings/Personal access tokens/Generate new token

Permissões do token, habilitando a opção repo

Provavelmente, habitalando somente a opção “public_repo” já deve funcionar, mas não testei esta opção. Se você quiser testar, pode deixar um comentário contando se funcionou.

Feito isso, clique em Generate token e uma tela vai abrir. Salve este token, ele é importante. Agora, precisaremos criar um Secret no nosso repositório.

Navegue para as configurações do seu repositório, e na seção Secrets, cadastre um secret com o nome CD_BLOG_TOKEN, com o token como valor.

Feito isso, basta fazer um commit na master e o workflow será disparado. Você pode acompanhar o resultado em Actions:

Lista de execuções dos workflows

Se o processo ocorrer sem problemas, você vai ver todos os jobs com sucesso e o seu blog vai estar atualizado.

Lista de execuções dos workflows

Agendamento de posts

Esta abordagem também permite a publicação automática de posts agendados. A resposta está no começo do workflow. Podemos configurar um agendamento através de uma expressão cron simples, como esta:

schedule:
  - cron:  '0 0 5 * *'

Esta expressão descreve a frequência de deploy nos seguintes termos:

┌───────────── minuto (0 - 59)
│ ┌───────────── hora (0 - 23)
│ │ ┌───────────── dia do mês (1 - 31)
│ │ │ ┌───────────── mês (1 - 12 ou JAN-DEZ)
│ │ │ │ ┌───────────── dia da semana (0 - 6 ou DOM-SAB)
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
* * * * *

Assim, nossa expressão 0 0 5 * * significa que o workflow deve ser disparado todos os dias, de todos os meses, às 5 horas da manhã.

No cabeçalho dos nossos posts, precisamos definir a data de postagem:

+++
author = "John Connor"
title = "Post agendado para o futuro longínquo"
date = "2032-07-03"
+++

Se publicarmos este post com data futura para o branch master, por padrão o Hugo não vai incluí-lo na geração do site. O workflow será executado todos os dias às 5 horas da manhã, até o dia 3 de julho de 2032, a data agendada para esta postagem, e ela será incluída na publicação do site deste dia em diante.

Trabalhando com branches

Por fim, esta abordagem também nos permite escrever os posts em branches individuais, sem marcar os posts com draft = true. Eu crio branches no formato post/nome-do-branch. Quando está pronto, é só fazer merge para o branch master que o processo vai ser disparado.

E é isso. Espero que essas dicas sejam úteis. E se você usa alguma outra estratégia ou tem alguma dica útil, compartilhe nos comentários.

Outras opções para compartilhar:
comments powered by Disqus