Pular para o conteúdo principal

Variáveis, Ambientes e Secrets

Vamos explorar como usar variáveis de ambiente e secrets dentro do nosso workflow.

O repo exemplo será um pouco difente do que usamos anteriormente que era um site usando node. Este será um servidor rest api que depende de variáveis de ambiente para funcionar. Ao rodar o código ele buscará variáveis que estão definidas no shell para completar suas constantes. É uma forma muito comum de buildar o código sem ter as variáveis setadas em algum lugar fora da máquina local que esta buildando o código.

data/database.js

// Setam variável constantes baseado no que esta definido no console
const clusterAddress = process.env.MONGODB_CLUSTER_ADDRESS;
const dbUser = process.env.MONGODB_USERNAME;
const dbPassword = process.env.MONGODB_PASSWORD;
const dbName = process.env.MONGODB_DB_NAME;

// Montar uma uri para o mongodb e cria um client com ela.
const uri = `mongodb+srv://${dbUser}:${dbPassword}@${clusterAddress}/?retryWrites=true&w=majority`;
const client = new MongoClient(uri);

playwright.config.js

  use: {
baseURL: `http://127.0.0.1:${process.env.PORT}`,
},

Variáveis

Podemos declarar variável em diferentes levels. Uma variável pode ser declarada para um workflow inteiro e caso a mesma variável seja declarada nos levels de jobs ou stesp ela será substituída.

Caso uma variável seja declarada em nível de workflow ela estará presente em todos os jobs em todos os steps. Se for declarada em um job estará em todos os steps deste job. Se for declarada a nível de step ela somente existirá dentro do step não sendo visível em steps subsequentes do mesmo job.

Vamos precisar de database free para mongodb. Já crie um usuário e uma senha e depois pegue na url de conexão o endereço.

Habilite em network access para que o banco seja acessado de qualquer ip, ou seja, 0.0.0.0/0.

name: Deployment
on:
push:
branches:
- main
- dev
env: # Nível de workflow
# O banco de dados possui o mesmo nome em todos os ambientes, mudando apenas a conta, por exemplo.
MONGODB_DB_NAME: gha-demo
jobs:
test:
#somente o job test tem acesso a estas variaveis.
# Crie um database
env:
MONGODB_CLUSTER_ADDRESS: cluster0.rnritqi.mongodb.net
MONGODB_USERNAME: davidpuziol
MONGODB_PASSWORD: ZHUFzBKmqO2GfUfy
PORT: 8080
runs-on: ubuntu-latest
steps:
- name: Get Code
uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-deps-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
# Preciamos que o servidor esteja rodando antes do próximo step para fazer os testes O comando expera que utilizemos uma porta. O shell nesse momento já estará com as variáveis de ambiente carregadas no job inteiro então é só referenciar diretamente.
- name: Run server
# Referenciamos assim por que estamos usando ubuntu-latest, se fosse windows era diferente.
run: npm start & npx wait-on http://127.0.0.1:$PORT
- name: Run tests
run: npm test
- name: Output information
run: |
echo "MONGODB_USERNAME: $MONGODB_USERNAME"
echo "MONGODB_USERNAME: ${{ env.MONGODB_USERNAME }}"
echo "MONGODB_DB_NAME: $MONGODB_DB_NAME"
echo "MONGODB_DB_NAME: ${{ env.MONGODB_DB_NAME }}"
deploy:
needs: test
runs-on: ubuntu-latest
steps:
# Não temos MONGODB_USERNAME neste job, então esperamos que somente imprima MONGODB_DB_NAME que está a nível de workflow.
- name: Output information
run: |
echo "MONGODB_USERNAME: ${{ env.MONGODB_USERNAME }}"
echo "MONGODB_DB_NAME: $MONGODB_DB_NAME"

Vamos analisar a saída no job test conseguimos acesso a tudo.

alt text

No deploy não tivemos acesso a variável do job test.

alt text

Existem algumas varíáveis default. Vamos colocar um ultimo step para ver isso. Além das default vars do github temos outras variáveis das coisas que vem instalada no ubuntu-latest por default.

  deploy:
needs: test
runs-on: ubuntu-latest
steps:
# Não temos MONGODB_USERNAME neste job, então esperamos que somente imprima MONGODB_DB_NAME que está a nível de workflow.
- name: Output information
run: |
echo "MONGODB_USERNAME: ${{ env.MONGODB_USERNAME }}"
echo "MONGODB_DB_NAME: $MONGODB_DB_NAME"
- name: Default Vars
run: env # <<<

O log.

0s
Run env
SELENIUM_JAR_PATH=/usr/share/java/selenium-server.jar
CONDA=/usr/share/miniconda
GITHUB_WORKSPACE=/home/runner/work/gh-fifth-vars/gh-fifth-vars
JAVA_HOME_11_X64=/usr/lib/jvm/temurin-11-jdk-amd64
GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_5d7b1057-4bf5-4332-9889-b3272ae532ab
GITHUB_ACTION=__run_2
JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64
GITHUB_RUN_NUMBER=5 # Numero da execução do workflow
RUNNER_NAME=GitHub Actions 6
GRADLE_HOME=/usr/share/gradle-8.9
GITHUB_REPOSITORY_OWNER_ID=32808515
ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=/opt/actionarchivecache
XDG_CONFIG_HOME=/home/runner/.config
DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
ANT_HOME=/usr/share/ant
JAVA_HOME_8_X64=/usr/lib/jvm/temurin-8-jdk-amd64
GITHUB_TRIGGERING_ACTOR=davidpuziol # Quem disparou o evento
GITHUB_REF_TYPE=branch
HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS=3650
ANDROID_NDK=/usr/local/lib/android/sdk/ndk/27.0.12077973
BOOTSTRAP_HASKELL_NONINTERACTIVE=1
***
PIPX_BIN_DIR=/opt/pipx_bin
STATS_TRP=true
STATS_BLT=true
GOROOT_1_20_X64=/opt/hostedtoolcache/go/1.20.14/x64
GITHUB_REPOSITORY_ID=838486259
DEPLOYMENT_BASEPATH=/opt/runner
GITHUB_ACTIONS=true
STATS_VMD=true
ANDROID_NDK_LATEST_HOME=/usr/local/lib/android/sdk/ndk/27.0.12077973
SYSTEMD_EXEC_PID=591
GITHUB_SHA=8e112ca8305a4077793be7aa0ae9fd49158c6f55
GITHUB_WORKFLOW_REF=davidpuziol/gh-fifth-vars/.github/workflows/deploy.yml@refs/heads/main
POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-ubuntu22
RUNNER_ENVIRONMENT=github-hosted
STATS_EXTP=https://provjobdsettingscdn.blob.core.windows.net/settings/provjobdsettings-0.5.181+6/provjobd.data
DOTNET_MULTILEVEL_LOOKUP=0
GITHUB_REF=refs/heads/main
RUNNER_OS=Linux
GITHUB_REF_PROTECTED=false
HOME=/home/runner # Já sabemos quem onde o ~ vai dar...
GITHUB_API_URL=https://api.github.com
LANG=C.UTF-8
RUNNER_TRACKING_ID=github_e1af56d0-dc5f-46f5-95ff-cfe245ecf20a
MONGODB_DB_NAME=gha-demo # Uma de nossas variáveis
RUNNER_ARCH=X64
GOROOT_1_21_X64=/opt/hostedtoolcache/go/1.21.12/x64
RUNNER_TEMP=/home/runner/work/_temp
GITHUB_STATE=/home/runner/work/_temp/_runner_file_commands/save_state_5d7b1057-4bf5-4332-9889-b3272ae532ab
EDGEWEBDRIVER=/usr/local/share/edge_driver
JAVA_HOME_21_X64=/usr/lib/jvm/temurin-21-jdk-amd64
GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_5d7b1057-4bf5-4332-9889-b3272ae532ab
GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json
INVOCATION_ID=d826e8b908d848808f920a99a6079a77
STATS_D=true
GITHUB_EVENT_NAME=push # Evento que gerou o workflow
GITHUB_RUN_ID=10255229296
JAVA_HOME_17_X64=/usr/lib/jvm/temurin-17-jdk-amd64
ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/27.0.12077973
GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_5d7b1057-4bf5-4332-9889-b3272ae532ab
HOMEBREW_NO_AUTO_UPDATE=1
GITHUB_ACTOR=davidpuziol
NVM_DIR=/home/runner/.nvm
SGX_AESM_ADDR=1
GITHUB_RUN_ATTEMPT=1
STATS_RDCL=true
ANDROID_HOME=/usr/local/lib/android/sdk
GITHUB_GRAPHQL_URL=https://api.github.com/graphql
ACCEPT_EULA=Y
RUNNER_USER=runner
STATS_UE=true
USER=runner
GITHUB_SERVER_URL=https://github.com
STATS_V3PS=true
PIPX_HOME=/opt/pipx
GECKOWEBDRIVER=/usr/local/share/gecko_driver
STATS_EXT=true
CHROMEWEBDRIVER=/usr/local/share/chromedriver-linux64
SHLVL=1
ANDROID_SDK_ROOT=/usr/local/lib/android/sdk
VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg
GITHUB_ACTOR_ID=32808515
RUNNER_TOOL_CACHE=/opt/hostedtoolcache
ImageVersion=20240730.2.0
DOTNET_NOLOGO=1
GITHUB_WORKFLOW_SHA=8e112ca8305a4077793be7aa0ae9fd49158c6f55
GITHUB_REF_NAME=main
GITHUB_JOB=deploy
XDG_RUNTIME_DIR=/run/user/1001
AZURE_EXTENSION_DIR=/opt/az/azcliextensions
PERFLOG_LOCATION_SETTING=RUNNER_PERFLOG
STATS_VMFE=true
GITHUB_REPOSITORY=davidpuziol/gh-fifth-vars
CHROME_BIN=/usr/bin/google-chrome
GOROOT_1_22_X64=/opt/hostedtoolcache/go/1.22.5/x64
ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/27.0.12077973
GITHUB_RETENTION_DAYS=90
JOURNAL_STREAM=8:18879
RUNNER_WORKSPACE=/home/runner/work/gh-fifth-vars
LEIN_HOME=/usr/local/lib/lein
LEIN_JAR=/usr/local/lib/lein/self-installs/leiningen-2.11.2-standalone.jar
GITHUB_ACTION_REPOSITORY=
PATH=/snap/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
RUNNER_PERFLOG=/home/runner/perflog
GITHUB_BASE_REF=
GHCUP_INSTALL_BASE_PREFIX=/usr/local
CI=true
SWIFT_PATH=/usr/share/swift/usr/bin
ImageOS=ubuntu22 # Ubuntão
GITHUB_REPOSITORY_OWNER=davidpuziol
GITHUB_HEAD_REF=
GITHUB_ACTION_REF=
GITHUB_WORKFLOW=Deployment # Nome do workflow que gerou esse job
DEBIAN_FRONTEND=noninteractive
GITHUB_OUTPUT=/home/runner/work/_temp/_runner_file_commands/set_output_5d7b1057-4bf5-4332-9889-b3272ae532ab
AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache
_=/usr/bin/env

Ter as variáveis com valores sensíveis injetadas no código dentro do nosso repositório público ainda não é bom. Para isso temos as secrets. Vamos alterar para pegar secrets que estejam definidas no repositório mas não no código.

  test:
# Serão injetadas as variáveis em tempo de execução
env:
MONGODB_CLUSTER_ADDRESS: ${{ secrets.MONGODB_CLUSTER_ADDRESS}}
MONGODB_USERNAME: ${{ secrets.MONGODB_USERNAME }}
MONGODB_PASSWORD: ${{ secrets.MONGODB_PASSWORD}}
PORT: 8080

alt text

alt text

Vamos rodar novamente substituindo agora valores que ninguém conhece. Uma coisa importante de se mencionar e testar é imprimir essas variáveis no console. Reza a lenda que Github não mostra o valor mesmo se você imprimir forçadamente. Vamos então forçar que imprima a secret de password para testar.

name: Deployment
on:
push:
branches:
- main
- dev
env:
MONGODB_DB_NAME: gha-demo
jobs:
test:

env:
MONGODB_CLUSTER_ADDRESS: ${{ secrets.MONGODB_CLUSTER_ADDRESS}}
MONGODB_USERNAME: ${{ secrets.MONGODB_USERNAME }}
MONGODB_PASSWORD: ${{ secrets.MONGODB_PASSWORD}}
PORT: 8080
runs-on: ubuntu-latest
steps:
- name: Get Code
uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-deps-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci

- name: Run server
run: npm start & npx wait-on http://127.0.0.1:$PORT
- name: Run tests
run: npm test
- name: Output information # Vamos tentar imprimir
run: |
echo "MONGODB_PASSWORD: ${{ secrets.MONGODB_PASSWORD}}
echo "MONGODB_PASSWORD: $MONGODB_PASSWORD
echo "MONGODB_PASSWORD: ${{ env.MONGODB_PASSWORD}}
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: Output information
run: |
echo "MONGODB_USERNAME: ${{ env.MONGODB_USERNAME }}"
echo "MONGODB_DB_NAME: $MONGODB_DB_NAME"
- name: Default Vars
run: env

E temos nossa saída. Parabéns GitHub!

alt text

Environment Secrets

Podemos ter diferentes ambientes que rodam o mesmo código mas essa variáveis mudam de acordo com o ambiente. No github podemos criar as variáveis por ambiente. Exemplo eu tenho dois ambientes aqui, testing e production.

alt text

Posso declarar as mesmas variáveis de mesmo nome em ambientes diferentes com valores diferentes.

alt text

alt text

alt text

Para usar as variáveis no workflow precisamos dizer qual o ambiente queremos. Por exemplo podemos rodar o job de test usando o enviroment de testing e deploy com variáveis de production.

jobs:
test:
# Se a variável estiver setada como global fora dos environment nesse caso será usado do environment testing. Então podemos deletar inclusive.
environment: testing
env:
MONGODB_CLUSTER_ADDRESS: ${{ secrets.MONGODB_CLUSTER_ADDRESS}}
MONGODB_USERNAME: ${{ secrets.MONGODB_USERNAME }}
MONGODB_PASSWORD: ${{ secrets.MONGODB_PASSWORD}}

alt text

Podemos usar diferentes environments em diferentes jobs. Podemos aplicar uma condição baseada em branches para setar as variavéis de production quando for na branch main e de testing na branch dev, mas veremos isso depois.

Podemos fazer mais com environments, não somente secrets. Podemos ter revisores especiais antes de iniciar o workflow.

Podemos adicionar um cronômetro de espera antes de cada job antes de ser iniciado.

Podemos adicionar algumas proteções para determinadas branches. Um exemplo seria que um job somente fosse iniciado se uma branch específica tivesse sido o evento que disparou o workflow.

alt text

No workflow que declaramos temos as seguintes branches no push

...
on:
push:
branches:
- main # Este dispararia o job test pela proteção que colocamos.
- dev # Este não dispararia o job test
jobs:
test:
environment: testing # <<<<<

Alteramos o workflow para usar o environment testing por exemplo e temos tudo funcionando.

alt text

Agora vamos criar uma branch dev e testar.

alt text