Tutorial: Continuous Integration de Pacote NuGet no Azure DevOps

Neste tutorial você vai aprender automatizar Continuous Integration para criar e publicar um Pacote NuGet em um Feed usando Azure DevOps sempre que uma branch for atualizada.

Tutorial: Continuous Integration de Pacote NuGet no Azure DevOps

Antes de começar, gostaria de dizer para os recém chegados, que o Azure DevOps, nada mais é, que o Visual Studio Online, o VSTS. Eles mudaram o nome, mas basicamente, as funções são as mesmas.

O tutorial será dividido em 3 tópicos principais. Se preferir pode pular um tópico ou outro.

A partir desse ponto, a leitura do tutorial é de 10 minutos, e sua aplicação leva 10 minutos.


#1 – Overview do tutorial

  • O tutorial será explicado em # Passo 01, # Passo 02… e assim por diante.
  • Em cada Passo, as ações Acesse, Abra, Configure e outras, estarão sempre no início do texto e em negrito.
  • Palavras-chave nos textos terão links a artigos deste Blog ou outros, sempre como referência.
  • A minha opinião/observação será feita sempre ao final de cada tópico.
  • O Pulo do gato, estará sempre em negrito e bem destacado conforme exemplo abaixo:

Pulo do Gato: Você pode baixar todo o código fonte deste tutorial sem ler absolutamente nada. Para fazer isso basta acessar meu github.

DESIGN PATTERNS vol.1 - Programação no Mundo Real - Fabio Silva Lima


#2 – Pré-requisitos para o tutorial.

Se você não está familiarizado com o Azure DevOps, vou tentar explicar de forma que você não fique perdido. Para realizar o tutorial, você vai precisar:

Eu apoio Fabio Silva Lima


#3 – Continuous Integration para criar e publicar Pacote NuGet no Azure DevOps

Esse ponto é o mais complexo de todos. Se você não está familiarizado com a parte de repositório de código fonte GIT no Azure DevOps, vou tentar dar uma passada geral para que você não fique perdido.

Até agora nós criamos o projeto FSL.Framework.Core em nosso computador, mas para prosseguirmos com a automação, precisamos que esse projeto esteja publicado no GIT no Azure DevOps. Então, antes de criarmos a automação para o pacote NuGet, vamos colocar o nosso projeto no GIT.

Se você não sabe colocar um projeto no GIT usando o Azure DevOps, siga os passos do meu outro artigo Tutorial: Controle seu Código Fonte de Graça com GIT no Azure DevOps e depois volte pra cá.

# Passo 01: Abra o seu projeto do Azure DevOps.

# Passo 02: Clique em Repos e Branches. Na branch master, clique no botão “…” e depois em New branch.

Clique em Repos e Branches. Na branch master, clique no botão ... e depois em New branch.

# Passo 03: Preencha o nome da branch como nuGet e clique em Create Branch. O nome da branch poderia ser qualquer outra coisa. Não utilizei padrão de nomenclatura de branches para esse artigo.

Preencha o nome da branch como nuGet e clique em Create Branch. O nome da branch poderia ser qualquer outra coisa. Não utilizei padrão de nomenclatura de branches para esse artigo.


#A Lógica da Automação Continuous Integration

A ideia é que, sempre que a branch nuGet for atualizada, automaticamente iniciará uma série de tarefas para compilar o projeto FSL.Framework.Core, rodar testes unitários, criar pacote nuget .nupkg, publicar pacote nuget diretamente no Azure DevOps sem precisarmos rodar nenhum command line em tempo de desenvolvimento. Esse processo de automação chama-se Continuous Integration.

# Passo 04: Acesse o Azure DevOps, entre no projeto Tutorial 2 e vá em Pipelines e depois Builds. Clique em New Pipeline.

Acesse o Azure DevOps, entre no projeto Tutorial 2 e vá em Pipelines e depois Builds. Clique em New Pipeline.

# Passo 05: Escolha a branch nuGet, pois é nela que realizaremos a configuração de Continuous Integration. Clique em Continue.

Escolha a branch nuget pois é nela que realizaremos a configuração de Continuous Integration. Clique em Continue.

# Passo 06: Escolha Empty job, pois vamos fazer do zero, sem template pré-definidos.

Escolha Empty job, pois vamos fazer do zero, sem template pré-definidos.

Nessa tela, estão todas as configurações para automatizar todo o processo. Preencha conforme imagem ou conforme descrito abaixo:

Pipeline Continuous Integration

  • Name: Build nuGet FSL.Framework.Core

Build nuGet FSL.Framework.Core

Agent job

  • Display name: Agent job 1
  • Demands: visualstudio e msbuild

Agent job


#Task: Nuget install

# Passo 07: Adicione uma task NuGet Tool Installer em Agent job. Para isso basta clicar no ícone +, localizar na lista (ou pela busca) e clicar no botão azul add. Essa será a primeira task a ser realizada, que é, instalar o NuGet. Clique na task do lado esquerdo e preencha conforme imagem ou conforme descrito abaixo:

Essa será a primeira task a ser realizada, que é, instalar o NuGet.

Nuget Tool Installer

  • Display name: Nuget install

Nuget install


#Task: Nuget restore

# Passo 08: Adicione uma task NuGet em Agent job. Para isso basta clicar no ícone +, localizar na lista (ou pela busca) e clicar no botão azul add. Essa será a segunda task, que é, restaurar os pacotes Nuget utilizados pela lib FSL.Framework.Core, que no caso são: Dapper e Newtonsoft JSON. Clique na task do lado esquerdo e preencha conforme imagem ou conforme descrito abaixo:

NuGet

  • Display name: NuGet restore
  • Command: restore
  • Path to solution, packages.config, or project.json: **/*.sln
  • Use packages from this Azure Artifacts/TFS feed: FabioSilvaLima

NuGet restore


#Task: Visual Studio Build

# Passo 09: Adicione uma task Visual Studio Build em Agent job. Para isso basta clicar no ícone +, localizar na lista (ou pela busca) e clicar no botão azul add. Essa será a terceira task, que é, compilar o projeto FSL.Framework.Core. Clique na task do lado esquerdo e preencha conforme imagem ou conforme descrito abaixo:

Visual Studio Build

  • Platform: $(BuildPlatform)
  • Configuration: $(BuildConfiguration)

Visual Studio Build


#Task: Nuget pack

# Passo 10: Adicione uma task NuGet em Agent job. Para isso basta clicar no ícone +, localizar na lista (ou pela busca) e clicar no botão azul add. Essa será a quarta task, que é, criar o pacote Nuget para FSL.Framework.Core. Clique na task do lado esquerdo e preencha conforme imagem ou conforme descrito abaixo:

NuGet

  • Display name: NuGet pack
  • Command: pack
  • Path to csproj:
    $(Build.SourcesDirectory)\**\*.csproj;!$(Build.SourcesDirectory)\**\*Test.csproj
  • Configuration to package: $(BuildConfiguration)
  • Package folder: $(Build.ArtifactStagingDirectory)
  • Package Options: Marque o campo ‘Include referenced projects’

NuGet pack


#Task: Nuget push

# Passo 11: Adicione uma task NuGet em Agent job. Para isso basta clicar no ícone +, localizar na lista (ou pela busca) e clicar no botão azul add. Essa será a quinta e última task, que é, publicar o pacote Nuget FSL.Framework.Core no repositório nuGet FabioSilvaLima do próprio Azure DevOps. Clique na task do lado esquerdo e preencha conforme imagem ou conforme descrito abaixo:

NuGet

  • Display name: NuGet push
  • Command: push
  • Path to NuGet package(s) to publish:
    $(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg
  • Target feed: FabioSilvalima
  • Allow duplicates to be skipped: Sim
  • Control options Continue on error: Sim

NuGet push


#Variables

# Passo 12: Clique em Variables e forneça chave e valor para as variáveis abaixo:

  • BuildConfiguration: release
  • BuildPlatform: any cpu

Obs.: Para cada uma, selecione o checkbox Settable at queue time.

Variables


#Triggers

# Passo 13: Clique em Triggers, selecione o checkbox Enable continuous integration e escolha a branch nuGet.

Pulo do gato: Essa é a configuração de automação, ou seja, quando essa branch nuGet for atualizada, esse Build nuGet FSL.Framework.Core será iniciado.

Triggers

# Passo 14: Clique em Options e preencha o campo Build number format para: $(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)

Options


#Save and Build

# Passo 15: Clique no botão Save and Queue para salvar e inicializar o Build. Vai abrir um popup e então clique no botão azul Save and Queue.

Save and Queue

O popup será fechado e vai aparecer uma mensagem e um link bem sutil no topo da tela, vide imagem acima.

Save and Queue

# Passo 16: Clique neste link sutil, que irá te mandar para a tela de Log desse build onde você poderá acompanhar cada uma das tarefas que estão ou serão executadas. Você pode clicar em cada uma das tarefas e ver o LOG correspondente. Se ocorrer algum erro, aparecerá em vermelho e o Build irá parar a execução. Quando a execução do Build parar devido a erros, normalmente chamamos de “quebrou o build”.


#Resumo até aqui

Nós configuramos o repositório do GIT no Visual Studio na pasta C:\inetpub\wwwroot\TFS\FabioSilvaLima\Tutorial2. Configuramos que alguns arquivos e pastas sejam ignorados. Demos o primeiro commit e push, subindo esse código fonte na branch master lá no Azure DevOps. Em seguida, criamos um Build no Pipeline de automação do Azure DevOps para fazer todo o processo para compilar projeto, criar e publicar o pacote nuGet.


#7 – Testar a automação no mundo real

Finalmente!

O teste no mundo real, que é alterar o código fonte no Visual Studio criando uma nova classe/feature no FSL.Framework.Core, fazer um commit local e um push, subindo essa alteração para o GIT no Azure DevOps.

Atenção: Nós estamos usando a branch master para fazer essa demonstração. Mas, nunca devemos usar a branch master como branch de desenvolvimento. A branch master deve ser uma cópia do código de produção, ou seja, ela contem o que há de mais atualizado em produção. Mas para facilidade para esse artigo estamos usando a branch master.

Inclua a referência da biblioteca System.Configuration do .NET Framework no projeto FSL.Framework.Core.

using FSL.CsharpUsefulExtensionsMethods;
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;

namespace FSL.Framework.Core.Repository
{
    public abstract class SqlRepository
    {
        private string _connectionStringId;
        private string _connectionString;

        public virtual string ConnnectionStringId
        {
            get
            {
                return _connectionStringId;
            }
        }

        protected async Task<T> WithConnectionAsync<T>(
            Func<SqlConnection, Task<T>> getData,
            Action<Exception> onException = null)
        {
            try
            {
                using (var connection = CreateConnection())
                {
                    await connection.OpenAsync();

                    return await getData(connection);
                }
            }
            catch (TimeoutException ex)
            {
                if (onException.IsNotNull())
                {
                    onException(ex);

                    return default(T);
                }

                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL timeout", ex);
            }
            catch (SqlException ex)
            {
                if (onException.IsNotNull())
                {
                    onException(ex);

                    return default(T);
                }

                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL exception (not a timeout)", ex);
            }
        }

        protected T WithConnection<T>(
            Func<SqlConnection, T> getData,
            Action<Exception> onException = null)
        {
            try
            {
                using (var connection = CreateConnection())
                {
                    connection.Open();

                    return getData(connection);
                }
            }
            catch (TimeoutException ex)
            {
                if (onException.IsNotNull())
                {
                    onException(ex);

                    return default(T);
                }

                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL timeout", ex);
            }
            catch (SqlException ex)
            {
                if (onException.IsNotNull())
                {
                    onException(ex);

                    return default(T);
                }

                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL exception (not a timeout)", ex);
            }
        }

        public SqlRepository UseConnectionStringId(
            string connectionStringId)
        {
            _connectionStringId = connectionStringId;

            return this;
        }

        public SqlRepository UseConnectionString(
            string connectionString)
        {
            _connectionString = connectionString;

            return this;
        }

        private SqlConnection CreateConnection()
        {
            if (string.IsNullOrEmpty(
                _connectionString))
            {
                _connectionString = ConfigurationManager
                    .ConnectionStrings[ConnnectionStringId]
                    .ConnectionString;
            }

            return new SqlConnection(
                _connectionString);
        }
    }
}

# Passo 17: Abra o arquivo FSL.Framework.Core.nuspec, altere o campo version para 1.0.2 e o campo releaseNotes.

Abra o arquivo FSL.Framework.Core.nuspec, altere o campo version para 1.0.2 e o campo releaseNotes

# Passo 18: Faça commit e push dessas alterações.

Faça commit e push dessas alterações


#8 – Pull Request

# Passo 19: Abra o Azure DevOps, vá até seu projeto, depois acesse Repos e Pull Requests. Clique no botão New pull request.

Repos e Pull Requests

# Passo 20: Escolha DE / PARA (origem/destino), branch master para nuGet e depois clique em Create.

Repos e Pull Requests

# Passo 21: Clique no botão Complete para completar o Pull Request, ou seja, o que foi feito na branch master irá para a branch nuGet. Ao fazer isso, será iniciado o Continuous Integration, que acionará o Build que fizemos no tópico anterior.

Repos e Pull Requests

Pulo do gato: Neste tutorial é você quem cria e completa/aprova o Pull Request, mas no Mundo Real, normalmente essa tarefa é delegada a outra pessoa.

# Passo 22: Ao clicar em Complete, vai aparecer um Popup, desmarque todos os checkboxes e clique em Complete merge. Nesse momento, para ver o Build em ação, acesse o menu esquerdo Pipeline e Builds.

Repos e Pull Requests

# Passo 23: Ao término do Build, vá em Artifacts, vai ver que aparece uma nova versão 1.0.2 disponível no pacote Nuget do FSL.Framework.Core. Essa versão 1.0.2, foi a nova versão que acabamos de entregar, ou seja, desenvolvemos a nova feature e a entregamos no Pull Request.

Repos e Pull Requests

Ufa! Acabou! Esse tutorial deu trabalho hein!
Preciso do seu feedback para podermos melhorá-lo.

Sugestões, críticas, deixe seu comentário abaixo para esse tutorial sobre Continuous Integration.

Até a próxima.


#Tutoriais relacionados com Continuous Integration

Tutorial: Controle seu Código Fonte de Graça com GIT e Azure DevOps

Tutorial: Criando um Pacote NuGet e Publicando no Azure DevOps

Faça download completo do código fonte no github.
Sobre o Autor:
Trabalha como arquiteto de soluções e desenvolvedor, tem mais de 18 anos de experiência em desenvolvimento de software em diversas plataformas sendo mais de 16 anos somente para o mercado de seguros.
Revisado por:
Apaixonado por tecnologia e sempre disposto a encarar novos desafios, atualmente trabalho focado em aplicações web e mobile com a plataforma .NET, e me aventurando nas diversas linguagens, desafios e experiências que a área nos proporciona.


Apaixonado por tecnologia, atualmente trabalho com aplicações web e estou aprofundando meu conhecimento em mobile. Meu objetivo é contribuir com a comunidade ajudando os desenvolvedores que estão iniciando.