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:
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:
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:
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):
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:
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