Pular para o conteúdo principal

Environment e Âncoras

Imaginemos que temos 2 branches (Protegidas) com a seguinte regra na nossa equipe de desenvolvimento.

  • develop: tudo que está nessa branch deve ser deployado no ambiente de develop.
  • main: essa branch será a produtiva.

Podemos definir a mesma variável, por exemplo, TOKEN que vamos utilizar para fazer alguma coisa. Porém se for no ambiente de desenvolvimento TOKEN=develop123 e se for no ambiente de produção TOKEN=B42LKJL592U0452.

Como podemos fazer isso?

Ao adicionar uma variável somente temos disponível all. No repositório, vá em Operate > Environments e crie dois environments; develop e production.

alt text

alt text

Existem muito mais funcionalidades em cima disso, mas por enquanto só precisamos definir esses ambientes e mais nada.

Agora crie a variável TOKEN para cada um dos ambientes pois poderá escolher.

alt text

alt text

Comecemos por esse esqueleto para falar de enviroment...

stages:
- check
- build
- deploy # Adicionado

deploy-develop:
stage: deploy
environment:
name: develop
script:
- echo "Deploy iniciando com TOKEN=$TOKEN"
rules:
- quando for aceito o merge para develop

deploy-production:
stage: deploy
environment:
name: production
script:
- echo "Deploy iniciando com TOKEN=$TOKEN"
rules:
- quando for aceito o merge para main

Até agora os nosso steps foram só de CI certo? Então eles rodam para conferir as coisas para que um merge possa ser aprovado. Depois de um merge ser aprovado e aceito que o código é mergeado de fato.

Se um merge for aceito o que isso significa?

Quando um merge request (MR) é aceito (merged), o que acontece é um push automático feito pelo GitLab para a branch de destino (develop, main, etc.) com o conteúdo resultante da merge.

Aceitar um MR = GitLab faz um git merge e dá um push na branch alvo

Isso irá gerar um outro pipeline com um novo evento. Nós vamos nos aprofundar ainda mais nisso, mas é bom que entenda esse cenário, então vamos preencher a nossa rule.

deploy-develop:
stage: deploy
environment:
name: develop
script:
- echo "Deploy iniciando com TOKEN=$TOKEN"
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'

deploy-production:
stage: deploy
environment:
name: production
script:
- echo "Deploy iniciando com TOKEN=$TOKEN"
rules:
- if: '$CI_COMMIT_BRANCH == "main"'

Com essa regra, se o commit for na main ou na develop, não importando de que forma aconteceu (direto ou via merge request), então o job irá participar do workflow da pipeline.

O correto é que viesse através de um merge, mas um git push direto de alguém poderia ser feito. Podemos contornar isso protegendo as branches, mas também poderíamos garantir nas rules caso alguém desproteja.

Ao criar o merge request temos o seguinte pipeline rodando.

alt text

O evento aqui foi de merge request para main e não de commit na main ou na develop.

Todo o workflow (sequência de jobs) da pipeline é gerado antes de criar os jobs. Porém antes de aceitar esse merge request que iria disparar o stage (fake) só de deploy vamos melhorar um pouquinho mais o nosso código e conhecer outro método de reaproveitamento de código.

Os dois jobs são muito parecidos podemos definir develop e somente ajustar a diferença no main. Vamos conhecer o conceito de âncora.

deploy-develop: &deploy-develop  # Definindo a âncora aqui
image: alpine
stage: deploy
script:
- echo "Deploy iniciando com TOKEN=$TOKEN"
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
environment:
name: develop

deploy-production:
<<: *deploy-develop # Herdando a configuração do deploy-develop
environment:
name: production # Mudando para o ambiente de produção
rules:
- if: '$CI_COMMIT_BRANCH == "main"' # Alterando a regra para a branch main

Aqui reaproveitamos pouco código, mas esse é um típico cenário que dá para enxugar muita coisa.

Extends vs Âncora

Extends e âncoras (& e <<) podem parecer semelhantes à primeira vista, pois ambos são usados para reutilizar configurações no GitLab CI, mas há algumas diferenças importantes entre eles.

O extends permite que um job herde configurações de outro job. O GitLab trata isso de uma forma mais voltada para herança de estrutura de jobs, como você faria com classes em programação orientada a objetos.

  • Mais fácil de mesclar a lista de extends.

As âncoras em YAML com o operador << são um recurso do próprio YAML e não são exclusivos do GitLab CI. Elas permitem que você repita configurações definindo uma âncora e referenciando-a em diferentes partes do arquivo YAML.

  • Uso mais flexível: Elas são mais genéricas e podem ser usadas para qualquer parte do YAML.
  • Permite merge de hashes: O operador << realiza um merge de hashes, ou seja, ele simplesmente incorpora o conteúdo de uma âncora em outro lugar, sem criar um vínculo tão explícito quanto o extends.
  • Simplicidade e leitura: Menos complexidade, já que você não está criando uma estrutura de herança, apenas está reutilizando um conjunto de configurações.

O YAML é processado de cima para baixo. Isso significa que é preciso primeiro criar a âncora para depois usá-la. PORÉM ISSO TAMBÉM É VALIDO PARA O EXTENDS, O TEMPLATE DEVE ESTAR DEFINIDO ANTES.

Exemplo de uso de âncora.

deploy-develop: &deploy-develop  # Definindo a âncora aqui
stage: deploy
image: alpine
script:
- echo "Deploy iniciando com TOKEN=$TOKEN"
environment:
name: develop
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'

deploy-production:
<<: *deploy-develop # Herdando a configuração do deploy-develop
environment:
name: production # Mudando para o ambiente de produção
rules:
- if: '$CI_COMMIT_BRANCH == "main"' # Alterando a regra para a branch main

Uma coisa que âncoras fazem que extends não faz. Exemplo bobo, mas que reflete tudo.

blocodechaves: &blocodechaves
name: production
scriptdeploy: &scriptdeploy
- |
echo "iniciando script"
echo "terminando script"
deploy-production:
stage: deploy
image: alpine
environment:
<<: *blocodechaves # Injeção direta sem precisar definir a estrutura inteira.
script:
<<: *scriptdeploy # Injeção direta só do código.
rules:
- if: '$CI_COMMIT_BRANCH == "main"'

Se fôssemos fazer igual usando o templates e extends é necessário definir toda a estrutura do yaml.

# Template apenas para o script
.script-template:
script: # Necessidade de passar o bloco do script
- echo "iniciando script"
- echo "terminando script"

# Template apenas para o environment
.environment-template:
environment: # Necessidad de passar o bloco do environmente
name: production

deploy-production:
extends:
- .script-template # Irá trazer o bloco inteiro
- .environment-template # Irá trazer o bloco inteiro
stage: deploy
image: alpine
rules:
- if: '$CI_COMMIT_BRANCH == "main"' # Condição para executar o job na branch main

Agora vamos falar da realidade. Busque sempre usar o extends por algumas razões:

  • É mais expressivo, confiável e melhor suportado pelo GitLab.
  • Âncoras não têm suporte a validação no GitLab e erros podem ser difíceis de debugar.
  • Extends é recurso nativo e mantido pelo GitLab, faz parte do mecanismo oficial de CI/CD.
  • O GitLab recomenda o uso de extends.
  • O GitLab não controla o comportamento do YAML, ele só interpreta o que chega.

Como ficaria o nosso resultado final de acordo com as boas práticas?

.deploy:
stage: deploy
image: alpine
script:
- echo "Deploy iniciando com TOKEN=$TOKEN"
environment:
name: develop
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'

deploy-develop:
extends: .deploy

deploy-production:
extends: .deploy
environment:
name: production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'

Porém poderíamos definir essa variável production em tempo de execução de acordo com a branch e já removemos esse template aí.

deploy:
stage: deploy
image: alpine
script:
- echo "Deploy iniciando com TOKEN=$TOKEN"
environment:
name: $DEPLOY_ENV
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
variables:
DEPLOY_ENV: develop
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
DEPLOY_ENV: production

Comita tudo e abre o merge para conferir as duas pipelines, uma do merge request e outra do commit assim que aceitar o merge.

alt text

alt text

alt text

Default

Se observarmos a definição abaixo, ela funciona como um template implícito, mesmo não sendo um .default. Uma vez definido, todos os jobs por default fazem o extends disso.

default:
tags:
- general
image: node:22-alpine