Este artigo aborda uma questão importante em projetos de desenvolvimento de software: manter as dependências atualizadas. Atualizar dependências fecha vulnerabilidades de segurança em potencial e nos permite usar recursos recentes e aplicar correções de bugs. Aqui, demonstro uma abordagem para atualizar dependências automaticamente em ambientes de CI/CD usando Renovate .
Qual problema o Renovate resolve?
Dê uma olhada em seus arquivos package-lock.json ou yarn.lock e você certamente perceberá que está lidando com centenas, senão milhares de dependências todos os dias. As dependências causam problemas mais cedo ou mais tarde:
Os esforços de manutenção aumentam com o tempo devido a alterações importantes, atualizações importantes, etc. Em algum ponto, pode não ser mais viável manter os projetos atualizados simplesmente porque muitas atualizações de dependência aparecem em um vulnerabilidades de segurança diárias tornam-se mais prováveis
Portanto, por um lado, você deve atualizar as dependências para versões mais recentes para utilizar novos recursos, se beneficiar de melhorias de desempenho ou fechar brechas de segurança. Por outro lado, atualizar dependências é uma tarefa tediosa que consome muito do tempo de sua equipe e atrasa o trabalho de melhorar seu produto e criar novos recursos.
Você normalmente se beneficia de atualizações oportunas que envolvem apenas pequenos saltos de versão porque há boas chances de que a atualização não interrompa sua construção. Esperar muito significa que sua equipe terá que se esforçar muito para realizar atualizações em massa, especialmente se grandes atualizações estiverem envolvidas.
Se você atualizar muitas dependências de uma vez, poderá ter os seguintes problemas:
Sua construção está quebrada-qual dependência é a culpada? Sua compilação está OK, você mesclou todas as dependências, mas sua implantação está quebrada-qual dependência causou isso?É justo dizer que não é sustentável realizar essas atualizações manuais de dependência periodicamente. Você precisa de suporte de ferramenta-graças a Deus, existe o Renovate!
Como o Renovate ajuda?
Renovar é um projeto de código aberto projetado para atualizar dependências automaticamente. Ele verifica os arquivos de pacote (por exemplo, package.json, pom.xml) de projetos atribuídos e cria solicitações de mesclagem (MRs) ou solicitações de pull (PRs), dependendo da convenção de nomenclatura de sua ferramenta de CI/CD (uso o termo MR daqui para frente ).
Você pode até mesmo empurrar o jogo tão longe que você pode ter o MR automaticamente mesclado quando o pipeline de CI estiver verde (ou seja, a compilação está OK, o linting está OK e todos os testes foram bem-sucedidos). O último é uma etapa em direção à implantação contínua, que pode ser um dos objetivos da sua equipe.
Observe que o Renovate não analisa a segurança de seus projetos como OWASP sim. Mas alguém poderia argumentar que se você mantiver suas dependências atualizadas, haverá um efeito positivo na segurança e as vulnerabilidades serão eliminadas mais cedo ou mais tarde. Obviamente, você pode combinar o Renovate com ferramentas especializadas para detecção de vulnerabilidade.
Como o Renovate se integra ao seu fluxo de trabalho?
O Renovate oferece suporte a muitos Ferramentas CI/CD e idiomas . Este artigo descreve como usá-lo com GitHub e GitLab no local.
Configuramos um “bot” Renovate que pode ser acionado manualmente ou automaticamente por um agendador. O bot verifica todos os projetos atribuídos e cria um ou mais MRs, dependendo de sua configuração e atualizações de dependência identificadas. O Renovate oferece várias maneiras de reduzir o ruído-por exemplo, definindo regras de grupo para combinar várias dependências em um MR ou para mesclar automaticamente MRs específicos.
O Renovate permite uma configuração refinada. Seu conceito de configuração é inspirado em ESLint ou Spring. Você pode definir configurações globais que são herdadas por cada configuração de projeto. Além disso, você pode definir configurações específicas do projeto: estender a configuração de base herdada e substituir as configurações existentes (por exemplo, automerge é definido como falso globalmente, mas você pode ativá-lo em uma configuração de projeto específica).
Você pode definir regras em muitos níveis: no nível do projeto, no nível do tipo de dependência (por exemplo, apenas para dependências de desenvolvimento) ou uma dependência específica (por exemplo, ignorar TypeScript> v4.2). Renovar busca o conceito de convenção sobre configuração. Isso significa que a configuração básica vem com muitas configurações significativas prontas para o uso. Além disso, você pode escolher a partir de listas compiladas de configurações (predefinições de configuração e predefinições de configuração completa).
Como veremos a seguir, Renovate fornece documentação diretamente em MRs ou notificações por e-mail, nos informando qual configuração está ativa e quais dependências estão prestes a serem atualizadas, bem como notas de versão embutidas e dicas para prosseguir.
Usando o aplicativo Renovate para GitHub
Configurando Renovar para GitHub significa instalar o Renovar aplicativo . A única coisa que você pode configurar é quais repositórios são verificados pelo bot Renovate (ou seja, aplicativo). Todas as outras configurações são definidas por código.
Instale o aplicativo Renovate no GitHub.
Após a instalação, você pode encontrar as definições de configuração na seção Aplicativos clicando na imagem do seu perfil> Configurações > Aplicativos .
Listando os aplicativos GitHub instalados.
Clique em Configurar e role até a parte inferior da página de configuração para alterar o acesso aos seus repositórios posteriormente.
Integração
Não se preocupe-O Renovate ainda não atualiza as dependências. Primeiro, você receberá um MR integrado em cada repositório ao qual concedeu acesso para Renovar. No meu exemplo, o bot analisa o único repositório configurado e descreve o que vai acontecer a seguir, então não há surpresas.
Como você pode ver na captura de tela abaixo, Renovate criou um MR integrado com o título “ Configure Renovate. ”
Renovar envia um PR de integração.
Se você abrir o PR, verá uma descrição muito detalhada do que acontece após a fusão.
Em primeiro lugar, você será notificado de que o Renovate detectou um arquivo package.json. O Renovate então aplica as predefinições de configuração padrão e lista a configuração concreta. Para fazer isso, o Renovate criará um arquivo de configuração específico do projeto (renovate.json). Como já mencionado, podemos alterar a configuração mais tarde.
Detalhes de PR onboarding.
Na seção “O que esperar”, Renovar descreve em detalhes quais dependências são atualizadas e como.
seção“ O que esperar ”no PR de integração.
Na verdade, isso diz que usamos a configuração padrão (config: base) fornecida pelo Renovate. O Renovate fornece predefinições de configuração padrão (por exemplo,: automergeDisabled) que podemos usar em nossa configuração, à medida que verei em breve. Além disso, ele agrupa várias predefinições em predefinições de configuração completa . config: base e config: semverAllMonthly são exemplos dessas predefinições de configuração completas.
Vamos mesclar isso para ativar o Renovate para nosso projeto.
As primeiras atualizações de dependência
Conforme descrito pelo MR de integração, mais dois MRs são criados.
Primeiros MRs após a integração.
Vamos dar uma olhada no primeiro MR, constituindo um MR de atualização de dependência concreto.
Uma primeira atualização de dependência MR.
O RM descreve em detalhes o que acontecerá. Neste projeto de exemplo, a dependência @ testing-library/user-event é atualizada para v13.1.9.
O que eu gosto é que você pode verificar sua configuração Renovate em Configuração seção. Por exemplo, nenhum automerge ainda está definido por causa da configuração padrão, então temos que mesclar o MR manualmente. Veremos como alterar isso mais tarde.
Além disso, se você expandir a seção, terá acesso às notas de lançamento.
Notas de versão são incorporadas aos MRs.
O segundo MR fixa dependências , ou seja, remove os intervalos de versão semântica. Este comportamento-você adivinhou- pode ser alterado .
Versões de intervalo de dependência obter removido pela fixação MR.
Os detalhes sobre a fixação são discutidos em detalhes nos documentos .
Estenda a configuração padrão
Depois de mesclar o MR de integração inicial, encontramos um arquivo renovate.json em nossa pasta raiz.
{“extends”: [“config: base”]}
No array extends, inicialmente uma predefinição de configuração completa ( config: base ) é definida que representa a configuração básica padrão para todos os idiomas. Essa predefinição de configuração completa é uma coleção de predefinições padrão. A seguir está um trecho de config: base:
{“extends”: [//…”: ignoreUnstable”,”: prImmediately”,”: automergeDisabled”,”: prHourlyLimit2″,”: prConcurrentLimit20″,”group: monorepos”,”group: recommended”,//…]}
Com esta configuração, Renovate está armado. Mas temos uma grande seleção de opções de configuração à nossa disposição, então vamos revisar nossa configuração.
{“extends”: [“config: base”],”automerge”: true,”automergeType”:”pr”,”timezone”:”Europe/Berlin”,”schedule”: [“depois das 15h todos os dias”,”antes das 5h da manhã todos os dias”]}
Substituímos o comportamento de mesclagem padrão definido por config: base (ou seja,: automergeDisabled) e instruímos o Renovate para mesclar MRs automaticamente.
Além disso, substituímos o comportamento de agendamento padrão, definindo um agendamento personalizado. O valor padrão para a programação é “a qualquer momento”, que funcionalmente é o mesmo que declarar uma programação nula; em outras palavras, o Renovate será executado no repositório o tempo todo. Definimos um cronograma para atualizar as dependências todos os dias entre 15h e 5h.
Vale a pena ler o nomes de fusos horários válidos , bem como Opções de programação de renovação . Também podemos usar uma das predefinições de programação , como agendamento: nonOfficeHours .
Automerging
Por padrão, Renovar apenas executa um automerge por padrão se você configurou um fluxo de trabalho com pelo menos um teste em execução; caso contrário, você precisa adicionar”requiredStatusChecks”: null à sua configuração. Se MRs exigem aprovações, isso representa outro obstáculo para a automergização. Nesse caso, você precisa usar o aplicativo auxiliar GitHub .
Removendo ruído
Se você digitalizar vários projetos com diferentes tecnologias envolvidas, o número de MRs pode rapidamente se tornar excessivo. Definir regras de automerging é uma ótima alavanca para solucionar isso.
Isso requer mesclar confiança ) trabalhando para uma alta cobertura de teste. Se isso não for possível atualmente ou apenas um objetivo de longo prazo, você pode argumentar que apenas dependências de nível de patch são geradas automaticamente porque o risco de quebrar seu aplicativo é gerenciável.
Para esse fim, você pode utilizar packageRules , um recurso poderoso que permite aplicar regras a pacotes individuais (por exemplo, apenas TypeScript> v4.2) ou para grupos de pacotes (por exemplo, apenas devDependencies de dependências em nível de patch) usando correspondência de padrão regex.
Por exemplo, poderíamos adicionar o seguinte packageRule para habilitar a geração automática apenas para patch dependências de nível:
“packageRules”: [{“updateTypes”: [“patch”],”automerge”: true}
Outra opção é agrupar dependências de acordo com regras definidas para reduzir o esforço para mesclagens manuais. O seguinte packageRule agrupa todas as devDependencies e dependências no nível do patch:
{“packageRules”: [{“matchDepTypes”: [“devDependencies”,”dependencies],”matchUpdateTypes”: [“patch”],”groupName”:”(dev) dependencies (patch)”}]}
No caso de um bug, entretanto, isso pode levar ao problema que você tem que rastrear qual atualização de dependência o causou.
A uma opção pragmática para reduzir o ruído é revisar seu programador e reduzir a frequência. No meu projeto, também usamos tecnologia para nos manter informados sobre as vulnerabilidades. Se uma violação de segurança for detectada, você ainda terá a chance de realizar atualizações manuais de dependência.
Usando Renovate com GitLab local
Se você executar GitLab internamente, esta seção descreve como colocar um bot Renovate em funcionamento. Nas seções a seguir, mostro um projeto GitLab constituindo um bot Renovate que cria MRs para outros projetos GitLab sempre que as dependências são encontradas em conformidade com a definição regras ed. Esta é uma etapa adicional em contraste com a seção anterior, onde usamos um aplicativo GitHub.
É importante entender que as configurações para seus repositórios (por exemplo, configuração de automerge) são idênticas à abordagem do GitHub. O fluxo de trabalho também é idêntico-integração, pin MRs, etc. A diferença está em como configurar o bot Renovate.
Criando um bot Renovate
Em contraste com o uso de Renovate com GitHub, precisamos fazer algum trabalho extra para permitir que nosso bot Renovate acesse outros repositórios GitLab e recupere as notas de lançamento do GitHub. Temos que criar um projeto GitLab dedicado constituindo o bot Renovate. Arquivamos isso instalando a ferramenta Renovate CLI manualmente como uma dependência npm.
Além disso, construímos um pipeline criando um arquivo.gitlab-ci.yml para executar a ferramenta Renovate CLI em nosso pipeline CI/CD. Nossa configuração do Renovate está localizada no arquivo config.js. A estrutura do projeto é semelhante a esta:
Estrutura do projeto do bot GitLab Renovate.
Antes de examinarmos o conteúdo dos arquivos, primeiro vamos cuidar de como acessar outros projetos GitLab. Para fazer isso, precisamos criar um token de acesso pessoal (PAT) para uma conta GitLab que tenha os direitos de acesso aos repositórios que desejamos que o Renovate analise.
Clique na foto do perfil do usuário e vá para o Seção Preferências . Em seguida, vá para a seção Tokens de acesso e crie um token com os escopos api, read_user e write_repository. Dê a ele um nome razoável e copie o token.
Prefiro não colocar o token diretamente no código-fonte do arquivo de pipeline (.gitlab-ci.yml) e, em vez disso, criar uma variável de ambiente. Vá para as Configurações de seu projeto de bot do Renovate, navegue até CI/CD e expanda a seção Variáveis . Clique em Adicionar variável , marque a variável de máscara , dê a ela um nome razoável e cole a PAT no campo de valor. No meu exemplo, uso o nome da variável GITLAB_PAT.
Então, posso usar a variável no arquivo.gitlab-ci.yml. O seguinte constitui todo o código de que precisamos para colocar o bot Renovate em funcionamento:
image: node: latest check_deps: script:-export RENOVATE_TOKEN=$ {GITLAB_PAT}-npm i-npm run check-dependencies
A primeira linha é importante para ter um ambiente Node disponível durante a execução do pipeline. Definimos uma etapa do pipeline check_deps. Na seção de script, precisamos definir uma variável de ambiente chamada RENOVATE_TOKEN com o PAT mencionado acima para conceder acesso Renovar aos repositórios que queremos processar.
Claro, posso nomear a variável CI/CD RENOVATE_TOKEN e pule a linha de exportação extra, mas prefiro essa maneira para melhorar a rastreabilidade. Para obter mais informações sobre GitLab CI/CD, você pode descobrir mais nos documentos oficiais .
Existem várias maneiras de configurar um bot Renovate auto-hospedado no GitHub, mas neste exemplo, optamos por fazê-lo com o npm. Instalamos todas as dependências com npm ie executamos um script npm chamado check-dependencies.
O arquivo package.json apenas adiciona Renovate como uma dependência dev e fornece um script npm para chamar a ferramenta Renovate CLI:
{“name”:”renovate-bot”,”devDependencies”: {“renovate”:”*”},”scripts”: {“check-dependencies”:”renovate”,}}
Nós escolhemos para usar * para instalar a versão mais recente sempre que o pipeline for executado. A configuração do Renovate está localizada em config.js:
module.exports={platform:’gitlab’, endpoint:’https://gitlab.com/api/v4/’, gitLabAutomerge: true, onboardingConfig: {extends: [‘config: base’],}, repositórios: [‘doppelmutzi/react-playground’], packageRules: [{matchUpdateTypes: [“patch”,”pin”], automerge: true}],}
O as primeiras três linhas são específicas do GitLab; o resto é idêntico à abordagem descrita acima.
Finalmente, você precisa adicionar o usuário à seção de membros de cada repo (ou ao grupo GitLab) com os direitos de criar MRs com a função Desenvolvedor ou Mantenedor.
Chame o bot Renovate manualmente
Podemos executar o bot manualmente iniciando o pipeline principal.
Executando o pipeline principal manualmente.
Clique em CI/CD e, em seguida, clique no botão Executar pipeline e execute o pipeline para o branch principal. Se a configuração estiver correta, a etapa do pipeline deve ser verde.
Configuração do pipeline principal com sucesso.
Executando o bot Renovate periodicamente
Você pode configurar diferentes aspectos do Renovate de maneiras diferentes. Como exemplo, descreverei uma abordagem alternativa para definir um cronograma para executar o Renovate periodicamente. Em vez da opção de programação de renovação, definimos uma programação de pipeline . Vá para a seção Cronogramas de CI/CD (cronograma do projeto) e crie um novo cronograma.
Execute Renovar periodicamente definindo uma programação customizada.
Com isso implementado, o pipeline principal de nosso projeto que representa o bot Renovate é executado diariamente às 2h.
Planejamento ativo para executar Renovar periodicamente.
Este pipeline será executado sempre que você se comprometer com o branch principal também.
Recuperando notas de versão do GitHub
Para integrar notas de versão em MRs como mostrado acima com o aplicativo GitHub Renovate, você precisa adicionar um PAT somente leitura . Na verdade, criar uma conta GitHub dedicada apenas para criar um PAT para Renovar é uma opção válida
Para ter um PAT em mãos, você precisa entrar no GitHub e ir para seção PAT nas configurações do desenvolvedor. Clique no botão Gerar novo token , dê uma nota razoável e verifique a opção public_repo na seção repo. Agora copie o token gerado.
Em seguida, criamos uma variável de ambiente CI/CD para integrá-la ao pipeline do GitLab sem revelar o token diretamente na base de código. Só precisamos ter certeza de que definimos um variável de ambiente chamada GITHUB_COM_TOKEN.
Em nosso projeto GitLab, navegamos para a seção CI/CD ( Configurações > CI/CD ) e expanda a seção Variáveis . Precisamos adicionar uma variável e colar nosso token GitHub gerado como o valor. Podemos usar GITHUB_COM_TOKEN como o nome e estamos prontos.
Prefiro dar a ele um nome diferente e criar essa variável de ambiente dentro de.gitlab-ci.yml para melhorar a rastreabilidade para meus colegas desenvolvedores. Digamos que eu criei uma variável chamada RELEASE_NOTES_GITHUB_PAT (também verifico a variável de máscara ). Eu o usaria da seguinte maneira:
check_deps: script:-export GITHUB_COM_TOKEN=$ {RELEASE_NOTES_GITHUB_PTA}-export RENOVATE_TOKEN=$ {GITLAB_PAT}-npm i-npm run check-dependencies
Com a variável de ambiente no lugar, as notas de versão são integradas a cada MR. A seção de variáveis CI/CD se parece com isto:
PATs para notas de lançamento e acesso de repos.
Aprovações automáticas e MRs
Conforme descrito na seção GitHub, o Renovate não pode mesclar MRs automaticamente quando você configura aprovações obrigatórias para solicitações de mesclagem. Em contraste com o uso do aplicativo GitHub Renovate, no momento da escrita, não há possibilidade de contornar esse obstáculo no GitLab, exceto configurar as aprovações como opcionais.
Capacidades de depuração
Se você tente uma nova configuração, você pode aumentar o nível de registro para depurar a fim de obter mais informações no Módulo de registro de renovação . Normalmente, é muito prolixo para uso diário.
Outra prática útil é executar um simulação em vez de realizar as operações reais. O extrato a seguir de.gitlab-ci.yml permite que o Renovate seja executado em modo seco para todos os ramos, exceto o master em combinação com um nível de log aumentado.
check_deps_dry_run: script:-export LOG_LEVEL=debug-export GITHUB_COM_TOKEN=$ {RELEASE_NOTES_GITHUB_PAT}-export RENOVATE_TOKEN=$ {GITLAB_PAT}-npm i-npm execute validate-config-npm execute check-dependencies—dry-run=true exceto:-master
Também é útil para validar o arquivo de configuração fornecido. O comando npm run validate-config acima chama um script npm denominado validate-config em package.json.
{“scripts”: {“check-dependencies”:”renovate”,”validate-config”:”renovate-config-validator config.js”}}
Ele utiliza a ferramenta renovate-config-validator embutida para verificar se há configurações incorretas em nosso arquivo de configuração. Você pode encontrar quaisquer problemas na saída do trabalho do pipeline.
Conflitos de mesclagem são corrigidos automaticamente
Se um MR for mesclado, mais cedo ou mais tarde surgirá a situação de que outro MR não poderá mais ser mesclado devido a conflitos com a mesclagem anterior.
Muitas vezes, o conflito está localizado nos arquivos package.json (várias entradas da mesma biblioteca com versões diferentes). Na próxima vez que o Renovate for executado, ele identificará e resolverá esses conflitos usando as versões mais recentes das dependências afetadas.
Resumindo, na maioria das vezes, você não precisa resolver esses conflitos manualmente.
Configuração final
Para encerrar este artigo, esta seção mostra a configuração final para o bot GitLab Renovate.
Aqui está o conteúdo de.gitlab-ci.yml:
image: node: last check_deps: script:-export GITHUB_COM_TOKEN=$ {RELEASE_NOTES_GITHUB_PAT}-export RENOVATE_TOKEN=$ {GITLAB_PAT}-npm i-npm executar validate-config-npm executar verificação de dependências apenas:-master check_deps_dry_run: script:-export LOG_LEVEL=debug-export GITHUB_COM_TOKEN=$ {RELEASE_NOTES_GITHUB_PAT}-export RENOVATE_TOKEN=$ {GITLAB_PAT}-npm i-npm run validate-config-npm run check-dependencies-true–dry-run exceto:-master
Nosso package.json se parece com isto:
{“name”:”renovate-bot”,”devDependencies”: {“renovate”:”*”},”scripts”: {“check-dependencies”:”renovate”,”validate-config”:”renovate-config-validator config.js”}}
E a configuração Renovar ( config.js) tem o seguinte formato:
module.exports={platform:’gitlab’, endpoint:’https://gitlab.com/api/v4/’, gitLabAutomerge: true, onboardingConfig: {extends: [‘config: base’],}, repositórios: [‘doppelmutzi/react-playground’], packageRules: [{matchUpdateTypes: [“patch”,”pin”], automerge: true}],}