Então você quer lançar uma library no Nuget.Org? Nesse post vou abordar Github Organization (Org), a integração do Org inteiro com seu Jenkins. No Jenkins vou falar de testes unitários, build, deploy, de projetos .NET Core usando Jenkins Pipeline/Jenkinsfile e publicação dos seus pacotes no MyGet e Nuget. Vou abordar cobertura de código e análise com SonarQube, tudo que estou começando a implantar nos meus projetos.
Muitos assuntos para um post só? Talvez, mas vale a pena conferir. E se você achar abstrato demais, não tem problema, tem exemplo prático ou links para as principais etapas. Embora ainda esteja implantando nos demais projetos, há um primeiro que está rodando com isso tudo. E está no github, com seu código aberto.
TL;DR
Eu não cheguei aqui seguindo um passo a passo, foram descobertas incrementais que me fizeram chegar aqui, mas o resultado é incrível. Mas enfim, quais resultados são esses?
Primeiro com um GitHub Organization eu pude criar uma integração direta com meu jenkins, de forma a usar um único folder dinâmico no jenkins para todo o Organization. Novos repositórios quando criados no Organization Oragon são configurados automaticamente e estão prontos para build na medida que ganham o Jenkinsfile. Com os webhooks do Organization, eu recebo no meu jenkins notificações sobre os eventos que demandam build, como a push e pull request. Os builds são disparados assim.
Olhando para cada build, este é feito com suporte do Jenkinsfile, versionado em cada repositório, permitindo não só evoluir o build de forma consistente junto com o projeto, permitindo sincronismo no versionamento, mas também habilitando a utilização de containers docker para a realização dos builds. Isso mesmo, embora meu jenkins esteja instalado no host, eu não preciso instalar runtimes para realizar builds, assim os builds são executados com containers docker. Esse processo me dá segurança, garantindo que um build não produza instabilidade no ambiente, nem demande instalações que poderiam comprometer não só outros builds, mas aplicações que rodam no mesmo servidor.
Com a adição recente do SonarQube ao pipeline, rodo diversas análises de qualidade no meu código a cada commit. Cobertura de Testes, Code Smells, Código duplicado e diversos débitos técnicos são checados a cada build.
Como resultado consigo ao preço de um clique, publicar pacotes nuget no NuGet.Org e myget, e planejo muito em breve realizar testes complexos que demandam setup de ambientes funcionais com Oracle, Postgres, MySQL, SQL Server, RabbitMQ e outros.
A proposta desse post é entregar essas features para que você faça o mesmo. São dias ou semanas de tempo poupado, caso tenha demandas parecidas com as minhas.
Contextualizando
Se você chegou até aqui, é possível que já conheça alguns dos projetos que cuido. Não são 1 nem 2, são vários. Ter o Jenkins como aliado ajuda muito. Hoje cada projeto tem seu pipeline de build e deploy configurado direto em um arquivo no repositório, permitindo total isolamento e independência na configuração do build. Enquanto de um lado o build e publish das minhas imagens docker é realizado pelo docker cloud, os pacotes nuget do Oragon são gerenciadas por esse fluxo. É sobre esse tipo de esteira que falarei hoje, é o que estou usando para gerenciar qualidade e cuidar da implantação desses pacotes. Esse post é um guia que lhe permitirá chegar exatamente ao nível de maturidade que eu cheguei. Assim eu não vou entrar em detalhes sobre gostos e quem é melhor na hora de entregar uma feature ou outra, vou mostrar quem eu uso. O stack completo é composto por:
- GitHub - Free
- GitHub Organization (Free)
- Servidor (Hetzner, pago)
- Jenkins (Open Source)
- Docker (Open Source)
- SonarQube (Open Source) + PostgreSQL (Open Source)
- MyGet (Free)
- Nuget.org (Free)
GitHub Organization
Muitos projetos são gerenciados nos próprios perfis dos seus fundadores no GitHub, o contra-ponto dessa estratégia é a utilização de um Org (organização) que permite que você centralize todos os repositórios de um determinado assunto, controlando permissões, webhooks, integrações de forma global. Existem ferramentas que sabem lidar com Organizações do GitHub, como no caso do Jenkins, e o resultado fica incrível. O segundo nível de segmentação é a separação em repositórios independentes. Esse tipo de segmentação permite controlar versões de forma independente.
A segmentação em repositórios deve ser planejada de acordo com o controle de versões/releases dos projetos. Agrupe projetos que precisem caminhar juntos, e isole projetos que possuem seus próprios ciclos. Minha estratégia com os Orgs Oragon e Docker Gallery é aglutinar os projetos afins. Cada um dos orgs possui diversos repositórios, que agrupam projetos. Cada repositório possui apenas 1 número de versão para todos os projetos.
Sobre o preço, o github não aplica políticas de preço para organizações, só são pagos repositórios privados, portanto você não terá encargos para criar organizações.
Então está aqui o passo a passo para criar seu primeiro Org no GitHub.
Jenkins
Fui apresentado ao Jenkins em 2010 e desde então nunca mais parei de usar. Jenkins é um servidor de automação, originalmente era considerado apenas um servidor de CI. Mas sua simplicidade lhe dá poderes incríveis. Como foi pensado para trabalhar com software, possui inúmeras features dedicadas a esse tipo de aplicação, como integração com os mais variados tipos de repositórios, suporte a análise de mudanças em versionadores, e de longe a mais usada ferramenta para integração contínua no mercado, trazendo consigo centenas de plugins, e integrações que enriquecem sua interface e adiciona integrações e controles excelentes para qualquer projeto. Se seu projeto está no Visual Studio Team Services ou no TFS você pode integrar seu pipeline com Jenkins ou realizar tudo pelo VSTS/TFS. No caso do case do Oragon, que é a proposta do post, vamos falar exclusivamente de GitHub e Jenkins.
Estratégia de Implantação
Diferente do Visual Studio Team Services, até existem ofertas de Jenkins as a Services, mas sem sombra de dúvidas a principal estratégia de implantação do Jenkins é com sua instalação on premise. Assim você tem a sua disposição a possibilidade de instalar no Windows, Linux e até possui imagem Docker para rodar seu Jenkins como um Linux Container. Embora eu fale muito de docker, eu uso jenkins instalado direto no sistema operacional. Exatamente para facilitar a gestão dos meus hosts docker, onde uso jenkins para realizar implantações, subir imagens, containers e stacks inteiros. Por isso optei por instalar o Jenkins direto no sistema operacional, que nesse caso é o Ubuntu. Sua única dependência é do JDK, resolvido facilmente via apt. Você encontra tudo que precisa para instalar o jenkins no ubuntu nesse link. O procedimento é de fato fácil.
Plugins necessários
Após instalar seu jenkins, você precisa alguns plugins, eles adicionam features que são importantes para o que queremos fazer.
- Plugins Recomendados (logo no setup você será questionado sobre os plugins que quer instalar, instale-os, e depois adicione os plugins abaixo)
- Docker Pipeline (Projeto, Wiki)
- GitHub Branch Source Plugin (Projeto, Wiki)
- Pipeline * (busque por "Pipeline:" e adicione todos, vale a pena ter)
Tutorial: Como adicionar plugins ao Jenkins.
Docker
Docker irá compor o stack de build servindo de pano de fundo para a execução das tarefas de build. Com auxílio do docker hub, usaremos imagens oficiais com os principais runtimes para executar nossas tarefas. Claro que instalar docker em um servidor de build também habilita a criação de ambientes virtuais para testes de integração. No nosso cenário, desde que não haja dependência direta com Windows, podemos subir ambientes completos e os mais variados stacks para compor nossos testes integrados. Desde ELK stack, RabbitMQ, Crossbar e muitos outros como já citei no início do post.
SonarQube
SonarQube é responsável por análise de qualidade de código, é ele quem avalia a qualidade do código com base em regras. Você pode adicionar o CodeCracker, usar o SonarC# ou ambos. Com o SonarQube você terá uma visão clara sobe percentual de cobertura e qualidade de código em geral. Ele avalia código duplicado, code smells, problemas de design e segurança de acordo com as configurações de análise que estiver usando.
Se você quiser rodar seu SonarQube no docker assim como eu, segue o link da imagem que contém todas as informações para a criação do seus containers (banco e aplicação).
Após o setup, não se esqueça de adicionar o SonarC# ou Code Cracker (pelo menos um deles). Você pode realizar a instalação via Sonar Marketplace, localizado na própria administração do SonarQube.
Se optar por utilizar o Code Cracker, não deixe de adicionar o pacote nuget ao teu projeto.
Nuget e MyGet
Nuget e MyGet são repositórios de pacotes, o Nuget serve exclusivamente de pacotes .NET Core, .NET Standard e .NET Framework, enquanto o MyGet suporta outros tipos, como pacotes NPM, chocolatey entre outros. Uma das vantagens do MyGet é isolar seus pacotes para um público controlado, evitando assim que uma nova versão cause grandes estragos em larga escala. O MyGet tem um delay muito menor do que o Nuget.Org, portanto em segundos vejo meu pacote publicado no MyGet, me permitindo usá-lo logo após os builds. Já no Nuget.Org, preciso esperar alguns minutos para ter meu pacote listado na plataforma, e esse comportamento dificulta o processo quando você quer usar imediatamente aquilo que acabou de publicar.
Uso a versão free do MyGet enquanto o Nuget.org é free mesmo.
A anatomia do build
O Jenkinsfile presente na raiz do repositório Oragon.AspNetCore.Hosting.AMQP descreve todo o build. Vou dissecar esse Jenkinsfile para mostrar como as coisas funcionam. Mas antes disso, vamos configurar o build no Jenkins. A imagem abaixo mostra como configurei o build de todo o Org do GitHub.
Com essa abordagem utilizo um único build para todo o Org. No Jenkins você enxerga seus projetos como se tivessem sido criados manualmente. Na prática basta configurar o build do org e pronto. O build dos projetos Oragon está disponível no link. foi de lá que tirei esse print.
Agora precisamos falar sobre o Jenkinsfile. Primeiro, se trata de um pipeline declarativo, versionado junto com o projeto, o que gera uma consistência excelente evoluindo junto com o projeto. Não sei se você já passou por isso, mas extrair uma versão antiga de um projeto par criar um fork em geral não é consistente com o build. E lembrar como era na época é uma tarefa chata. Com essa estratégia você tem uma foto completa do build e do código. quer algo mais consistente que isso? Outro ponto é que o build não acontece sem o commit, portanto nada de configuração de build não versionada. Dá para falar por horas sobre as vantagens de ter tudo junto.
Agent
Vamos às possibilidades. O papel de seção Agent no Jenkinsfile é especificar requisitos de um agente. Uma entre as diversas configurações é a que permite usar agentes docker. Isso faz com que seu build seja executado em um container, com base na imagem que especificar ou em um dockerfile que gere a imagem necessária para rodar seu build.
Para o build dos projetos Oragon, eu comecei usando a versão abaixo:
agent { docker { alwaysPull false image 'microsoft/dotnet:2.1-sdk' reuseNode false args '-u root:root' } }
Até esse momento eu não precisava de um dockerfile para a criação do runtime, bastava usar a imagem microsoft/dotnet:2.1-sdk para realizar build e testes. No entanto para adicionar a integração com SonarQube era necessário instalar ferramentas globais ao runtime do .NET Core. Vou detalhar isso quando falar do Dockerfile que uso para o runtime do build, por hora vamos falar de como especificar que seu agent depende de um dockerfile.
agent { dockerfile { args '-u root:root' } }
Com essa configuração espera-se que haja um Dockerfile no root do repositório, ao lado do Jenkinsfile. Estou forçando a utilização do usuário root para simplificar algumas tarefas de gestão de permissões no container. Aqui está o Dockerfile que uso. Note que preciso instalar o JDK, dotnet-sonarscanner e coverlet.console. São os principais recursos para tornar toda a integração possível.
Se você quer saber mais sobre Jenkins pipeline e docker acesse esse link.
Stages
Agora o próximo passo a ser configurado no Jenkinsfile são os estágios de build. Cada estágio possui seus steps configurados. Com o Jenkinsfile aberto (tem link acima) você deve olhar para o Stage "Build".
stage('Build') { steps { echo sh(script: 'env|sort', returnStdout: true) sh 'dotnet build ./Oragon.AspNetCore.Hosting.AMQP.sln' } }
Nesse passo simplesmente valido se meu projeto está compilando.
stage('Test') { steps { withCredentials([usernamePassword(credentialsId: 'SonarQube', passwordVariable: 'SONARQUBE_KEY', usernameVariable: 'DUMMY' )]) { sh ''' export PATH="$PATH:/root/.dotnet/tools" dotnet test ./tests/Oragon.AspNetCore.Hosting.AMQPTests/Oragon.AspNetCore.Hosting.AMQPTests.csproj --configuration Debug --output ../output-tests /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput='/output-coverage/coverage.xml' /p:ExcludeByFile="tests/**" dotnet sonarscanner begin /k:"Oragon-AspNetCore-Hosting-AMQP" /d:sonar.host.url="http://sonar.oragon.io" /d:sonar.login="$SONARQUBE_KEY" /d:sonar.exclusions=tests/** /d:sonar.cs.opencover.reportsPaths="/output-coverage/coverage.xml" /d:sonar.test.exclusions="tests/**" dotnet build ./Oragon.AspNetCore.Hosting.AMQP.sln dotnet sonarscanner end /d:sonar.login="$SONARQUBE_KEY" ''' } } }
Já no estágio seguinte, Test, executo os procedimentos necessários para testar e submeter as métricas para o SonarQube. Essa foi a etapa que mais tomou tempo no aprendizado, pois nunca havia feito o build de um projeto .NET Standard com geração de métricas e integrando tudo com o SonarQube. Sem sombra de dúvidas esse post lhe eliminará dezenas de builds quebrados antes do primeiro build de sucesso com a nova configuração.
Por fim temos o Pack e Publish, que consistem em gerar os pacotes nuget e publicá-los no myget e nuget.org.
Minha estratégia é a seguinte:
- Build de branch não publica pacotes
- Builds de tags publicam pacotes
- se o nome da tag terminar com "-alpha" então temos uma release alpha, que deve ser publicada apenas no myget (builds alpha incluem símbolos e são publicados em mode debug)
- se o nome da tag terminar com "-beta" então temos uma release beta, que deve ser publicada tanto no myget quanto no nuget (builds beta são publicados em modo release, sem símbolos)
- A convenção que criei determina que se a tag não possuir prefixo, então estamos falando de um build RTM (builds RTM ou release to market são publicados em modo release, sem símbolos) e como resultado são produzidos pacotes que são publicados tanto no nuget.org quanto no myget.
Esses controles são feitos com when { buildingTag() } e if (env.BRANCH_NAME.endsWith("-alpha")). Vale lembrar que o Jenkinsfile é basicamente um conjunto de instruções groovy.
Conclusões
Esse workflow me permite controlar muito bem como o fluxo de desenvolvimento ocorre, aplicando o mínimo de qualidade para os projetos que trabalho. Esse pipeline está completo no projeto Oragon.AspNetCore.Hosting.AMQP enquanto os demais projetos do Org Oragon ainda não estão no Sonar, mas em breve estarão.
Você precisará configurar as credenciais do MyGet, do Nuget e do SonarQube no teu Jenkins. Não embarque tais credenciais no código nem no Jenkinsfile. Os plugins do jenkins conseguem fazer com que os builds mascarem tais credenciais no output. Assim o output de build não revela credenciais, o que poderia ser desastroso.
Esse processo pode parecer muito complexo, no entanto não é. É extenso para um único post. Uma dica é fazer uma coisa de cada vez, adicionar uma feature por vez.
Se você está decidido a não fazer isso, entenda, esse pipeline me poupa muito tempo, principalmente pois eu não lido com a criação de features para esses projetos todos os dias, as vezes passo meses sem olhar para esses projetos, portanto é muito importante para mim não precisar lembrar de passos chatos e burocráticos. Embora haja alguma burocracia, como a necessidade de um commit para que tudo aconteça, essa é a garantia de que as versões nascem apenas de uma fonte confiável.
Não se assuste nem desista. Builds complexos são chatos de configurar mesmo. Isso toma tempo, mas o resultado poupa muito mais tempo e entrega muito mais qualidade e automação.
Esse processo de build está em constante modificação, ainda preciso adicionar steps para gerar documentação dos projetos, gerar site estático, enfim, na data da publicação temos apenas esses steps, dispostos dessa forma. Não se espante caso olhe para um determinado link e veja o Jenkinsfile com novos elementos ou organizado de outra forma. Estamos em constante evolução.
Prints
Tirei alguns prints para mostrar como as coisas funcionam no dia-a-dia, como você vê teu Org inteiro no Jenkins, o que você encontra no SonarQube.
Links
Jenkins | http://jenkins.oragon.io/job/oragon/job/oragon-github/
SonarQube | http://sonar.oragon.io/
Suas dúvidas ajudam a incrementar o post
Esse tende a ser um post incremental, onde de acordo com as dúvidas, vou incrementando o texto e melhorando as explicações. Caso tenha dúvidas ou sugestões a respeito do post, comente.
Utilizarei-o para lembrar de como cheguei às configurações, é bom para detalhar aquelas coisas que a gente só faz uma vez e esquece, por isso incrementarei na medida do possível por esse motivo também. É uma forma de documentar aquilo que vou fazendo.
Gostou? Comente!
Dúvida? Comente!
Nosso papel é ajudar e espero que possa ser útil para você e/ou teu projeto.
Update 02/10/2018 - Trigger para tags
Esse processo está rodando há algum tempo e havia uma única pendência: Realizar build automático da tag. Entenda: o Jenkins reconhecia a nova tag, mas a trigger que deveria fazer o build da tag iniciar automaticamente não era disparado. Como resultado eu tinha a tag no Jenkins, mas precisava entrar no jenkins e pressionar o botão de build. Uma burocracia que não deveria existir.
Eu não fui o único a sofrer com isso, e as issues estão aqui JENKINS-47496 e JENKINS-53432 (a principal está aberta ainda). A solução veio de um dos comentários que aponta para a adição de um novo plugin que só faz isso: Após instalar Basic Branch Build Strategies Plugin tive de adicionar um detalhe ao build e agora não tenho mais pendências.
0 comentários