Featured image

Nota: Este texto foi originalmente publicado no meu blog no Medium em setembro de 2022.

Cenário: Link para o cabeçalho

Estamos desenvolvendo um SPA com uma arquitetura robusta e queremos disponibilizá-lo em três ambientes diferentes: Desenvolvimento, Homologação (QA) e Produção (podemos também ter dois ambientes de produção). Cada versão do App deve consumir as APIS de seus respectivos ambientes.

Problema: Link para o cabeçalho

O empacotamento não permite alterar as variáveis de ambiente em tempo de execução.

Solução: Link para o cabeçalho

Existem algumas soluções disponíveis, mas aqui trataremos de uma bem simples de ser aplicada. Nesse caso em específico estaremos realizando as alterações em um projeto iniciado a partir do “create-react-app”, mas nada impede da solução ser utilizada com outras tecnologias, como Vue.

1 — Organização de e armazenamento das variáveis de ambiente Link para o cabeçalho

Vamos começar criando 3 arquivos (um para cada ambiente) na pasta pública:

React Env Vars

Dentro dos arquivos vamos salvar os valores dentro do objeto Window, que representa a janela do navegador. Nossas variáveis serão salvas dentro do atributo ENV (o nome aqui não faz diferença, poderia ser window.VARIAVEIS ou window.API).

Nossos arquivos (config-dev, config-qa e config- prod) devem ficar, com suas respectivas rotas, assim:

React Env Vars

Agora precisamos criar um config.js na raiz da pasta pública, que é onde nosso app irá consumir as rotas. O Arquivo pode ficar vazio por enquanto, pois vamos gerar seu conteúdo automaticamente a partir dos arquivos que criamos anteriormente.

No fim nossa pasta pública ficou assim:

React Env Vars

2 — Declarar config.js antes do React ser executado Link para o cabeçalho

Precisamos chamar nosso config.js diretamente no index.html para salvar nossas variáveis no navegador antes que os serviços sejam executados (o que resultaria em undefined):

React Env Vars

3 — Configurar serviços Link para o cabeçalho

Agora precisamos fazer com que nossos serviços consumam as rotas salvas no objeto window. Por organização vamos criar uma pasta envinroments dentro do nosso projeto com um único arquivo index.js e apenas uma linha de código:

React Env Vars

Agora com um simples import ENV from '@enviroments' podemos ter acesso as rotas e consumi-lás em nossos serviços.

4 — Configurar ambiente de desenvolvimento local Link para o cabeçalho

Precisamos configurar nosso ambiente de desenvolvimento local para que seja possível utilizar o comando npm run {env} e gerar automaticamente o conteúdo do arquivo config.js que criamos anteriormente.

Vamos executar npm install cpy-cli para insalar a lib cpy-cli, que por sua vez nos permite copiar e renomear arquivos.

Agora vamos configurar os scripts em package.json:

"scripts": {  
"dev": "npm run env:dev && node scripts/start.js",  
"qa": "npm run env:qa && node scripts/start.js",  
"prod": "npm run env:prod && node scripts/start.js",  
"env:dev": "cpy ./public/config/config-dev.js ./public/ --rename config.js",  
"env:qa": "cpy ./public/config/config-qa.js ./public/ --rename config.js",  
"env:prod": "cpy ./public/config/config-prod.js ./public/ --rename config.js",  
"start": "npm run env:dev && node scripts/start.js",  
"build": "node scripts/build.js",  
"test": "node scripts/test.js"  
} 

Explicação:
Quando executarmos um npm run {env}nosso script irá copiar o arquivo config-{env} para a raíz da pasta pública e renomear para config.js, substituindo o atual arquivo (que neste caso está vazio).

5 — Shell Script e Docker Link para o cabeçalho

Criaremos um shell script (por organização, o mesmo poderia ser passado diretamente no dockerfile):

#!/usr/bin/env bash
ARG1="$1"
echo "Configurando ambiente de ${ARG1}"
cp "/opt/bitnami/nginx/html/config/config-${ARG1}.js" /opt/bitnami/nginx/html/config.js
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Remova o comentário desta linha para debug
# Carregar bibliotecas
. /opt/bitnami/scripts/libbitnami.sh
. /opt/bitnami/scripts/libnginx.sh
# Carregar variáveis de ambiente NGINX
. /opt/bitnami/scripts/nginx-env.sh
print_welcome_page
info "** Starting NGINX setup **"
/opt/bitnami/scripts/nginx/setup.sh
info "** NGINX setup finished! **"
/opt/bitnami/scripts/nginx/run.sh
echo ""
exec "nginx -g daemon off;"

e vamos salvar como environment-tag.sh dentro da nossa pasta config do diretório público.

Explicação:
Na hora de executar o container, docker run, vamos passar um parâmetro (neste caso dev, qa ou prod), nosso arquivo shell vai receber esse parâmetro, atribuir a variável ARG1 e a partir disso copiar o respectivo arquivo(config-dev, config-qa ou config-prod) para a raíz da pasta pública (que definitivamente será consumida).

Agora precisamos configurar nosso dockerfile para executar o shell script:

FROM node:12.14.0 as build-stage
WORKDIR /usr/
COPY package*.json public config scripts src ./  ./
RUN npm install && \
npm run-script build
FROM bitnami/nginx:1.17
COPY --from=build-stage /usr/build/ /opt/bitnami/nginx/html
COPY nginx.conf /opt/bitnami/nginx/conf/nginx.conf
USER 0:0
RUN ["/bin/bash", "-c", "chmod +x /opt/bitnami/nginx/html/config/environment-tag.sh"]
ENTRYPOINT ["/opt/bitnami/nginx/html/config/environment-tag.sh"]
CMD ["dev"]

Explicação:
USER executa chmod como root.
ENTRYPOINT executa o shell script.
CMD define um parâmetro padrão, que pode ser alterado, por linha de comando, na hora de executar o container: docker run [nome-da-imagem] {arg}.

Pronto. Agora podemos economizar tempo gerando apenas uma imagem e fazendo quantos deploys quisermos em quantos ambientes quisermos:

docker build --tag projeto:1.0 . // build
docker run -ti projeto {env} // substituir {env} por dev, qa ou prod