.NET

27 dez, 2018

CI-CD com Travis e AppVeyor usando Cake e .Net Core

Publicidade

No artigo anterior usamos o Cake para fazer build, executar testes, gerar pacotes e deploy no repositório oficial do Nuget através de um script de tarefas. Se você não viu, corre lá e depois volte aqui.

Hoje usaremos esse script em conjunto com duas ferramentas de CI/CD muito populares: o Travis e AppVeyor, pois não queremos ficar executando esse script de forma manual em nosso próprio computador.

Ambas as ferramentas são gratuitas para projetos open source e oferecem planos pagos para projetos privados. Você vai precisar de uma conta de usuário em cada uma delas.

Agora você deve estar se perguntando o motivo de usarmos duas ferramentas de CI que fazem praticamente as mesmas coisas. Bom, o Travis roda em ambiente Linux e OSX. Já o AppVeyor roda apenas em ambiente Windows. Dessa forma, vamos garantir a portabilidade do código em diferentes plataformas.

Configurando o Travis

O Travis é uma ferramenta muito popular no mundo JavaScript, Ruby, Python, entre outros, e agora também tem suporte ao .Net Core.

Vamos criar um arquivo de configuração do Travis na raiz do repositório com o nome .travis.yml.

language: csharp
os: linux
sudo: required
dist: trusty
addons:
  apt:
    packages:
    - gettext
    - libcurl4-openssl-dev
    - libicu-dev
    - libssl-dev
    - libunwind8
    - zlib1g
dotnet: 2.0.0
mono: latest
env:
  - DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true DOTNET_CLI_TELEMETRY_OPTOUT=true

# You must run this command to give Travis permissions to execute the build.sh shell script:
# git update-index --chmod=+x build.sh
script:
  - ./build.sh

Para que o Travis possa executar o script build.sh, será necessário dar permissão de execução nesse arquivo em nosso repositório:

Agora é necessário ativar o repositório no Travis. Para isso, acesse sua conta e com isso você verá uma lista de repositórios públicos do seu GitHub. Ative o repositório que deseja configurar.

Veja que foi disparada uma trigger no Travis que inicia a execução do nosso script build.sh.

Repare que essa execução falhou!

Verificando os logs podemos perceber que ele passou pelas etapas de build, testes, criação de pacote Nuget, mas falhou ao tentar fazer o deploy do pacote no nuget.org. Essa falha ocorreu pois a chave de api do Nuget que foi configurada no arquivo build.cake tinha tempo de expiração para um dia e já não é mais válida. Lembre-se, essa chave foi utilizada no artigo anterior.

Calma! Corrigiremos isso mais adiante. Neste momento podemos assumir que o Travis está configurado corretamente, pois ele fez o que deveria: executou nosso script.

Configurando o AppVeyor

O AppVeyor, por outro lado, é bem popular para quem usa a plataforma .Net, pois ele roda em ambiente Windows e sempre suportou o .Net (full) Framework, e agora também o .Net Core.

Assim como fizemos para o Travis, também devemos criar um arquivo de configuração para o AppVeyor na raiz do repositório com o nome appveyor.yml.

version: '{build}'
pull_requests:
  do_not_increment_build_number: true
environment:
  DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
  DOTNET_CLI_TELEMETRY_OPTOUT: true
  NUGET_API_KEY:
    secure: hQ5LogWZGZTKK4u/AlC/X4ThwiCq0JbYNxuOCVMFWzRJwH8VSYHuVry/At69M1kH
build_script:
- ps: .\build.ps1
test: off
artifacts:
- path: .\artifacts\**\*.nupkg
  name: NuGet

Aqui também será necessário ativar o repositório no AppVeyor. Para isso, acesse sua conta, clique no menu Projects e em seguida, clique no botão New Project, conforme abaixo:

Agora selecione o repositório na sua lista do GitHub:

Com isso, o AppVeyor já consegue executar nosso script build.ps1.

Nosso build no AppVeyor também falhou pelo mesmo motivo da falha no Travis: a chave de api que estamos usando para o Nuget já não é mais válida para fazer deploy do pacote.

Criando uma nova chave de Api do Nuget

Para começarmos a corrigir o problema, precisamos criar uma nova chave de Api no nuget.org. Para isso, basta acessá-lo com sua conta, clicar no seu nome de usuário no canto superior direito e clicar na opção Api Keys.

Preencha as informações necessárias. No meu caso, dessa vez deixei o período máximo para expiração, que é de 1 ano.

Após gerar a nova chave, basta copiá-la.

Protegendo a chave de publicação no AppVeyor

Usaremos o AppVeyor para fazer deploy do nosso pacote Nuget, mas não podemos deixar a chave exposta, conforme visto no artigo anterior. Usaremos as variáveis de ambiente criptografadas no AppVeyor.

Para isso, o AppVeyor conta com a ferramenta Encrypt Configuration Data, que nos permite criptografar qualquer informação em suas variáveis de ambiente.

Apenas cole a chave de Api gerada no nuget.org e clique no botão Encrypt.

Copie a nova chave criptografada e cole no arquivo de configuração do AppVeyor, dentro da seção **environment** do arquivo.

Dessa forma, garantimos que nenhum engraçadinho que tenha acesso ao nosso repositório de código possa usar essa chave para publicar pacotes indevidos em nosso repositório Nuget.

Perceba que adicionamos a nova chave de publicação de pacotes Nuget apenas no arquivo de configuração do AppVeyor, já que não faz sentido as duas ferramentas de CI/CD criarem os mesmos pacotes e fazer deploy no nuget.org.

Neste caso, escolhi o AppVeyor para criar e fazer deploy dos pacotes apenas por gosto mesmo. Você pode usar o que gostar mais.

Ajustando o script Cake para rodar em ambiente de CI/CD

Para que os erros anteriores de execução não voltem a ocorrer, será necessário fazer alguns ajustes em nosso script build.cake.

#tool "nuget:?package=GitVersion.CommandLine"
#addin nuget:?package=Newtonsoft.Json

using Newtonsoft.Json;

var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var artifactsDirectory = MakeAbsolute(Directory("./artifacts"));

Setup(context =>
{
     CleanDirectory(artifactsDirectory);
});

Task("Build")
.Does(() =>
{
    foreach(var project in GetFiles("./src/**/*.csproj"))
    {
        DotNetCoreBuild(
            project.GetDirectory().FullPath, 
            new DotNetCoreBuildSettings()
            {
                Configuration = configuration
            });
    }
});

Task("Test")
.IsDependentOn("Build")
.Does(() =>
{
    foreach(var project in GetFiles("./tests/**/*.csproj"))
    {
        DotNetCoreTest(
            project.GetDirectory().FullPath,
            new DotNetCoreTestSettings()
            {
                Configuration = configuration
            });
    }
});

Task("Create-Nuget-Package")
.IsDependentOn("Test")
.WithCriteria(ShouldRunRelease())
.Does(() =>
{
    var version = GetPackageVersion();

    foreach (var project in GetFiles("./src/**/*.csproj"))
    {
        DotNetCorePack(
            project.GetDirectory().FullPath,
            new DotNetCorePackSettings()
            {
                Configuration = configuration,
                OutputDirectory = artifactsDirectory,
                ArgumentCustomization = args => args.Append($"/p:Version={version}")
            });
    }
});

Task("Push-Nuget-Package")
.IsDependentOn("Create-Nuget-Package")
.WithCriteria(ShouldRunRelease())
.Does(() =>
{
    var apiKey = EnvironmentVariable("NUGET_API_KEY");
    
    foreach (var package in GetFiles($"{artifactsDirectory}/*.nupkg"))
    {
        NuGetPush(package, 
            new NuGetPushSettings {
                Source = "https://www.nuget.org/api/v2/package",
                ApiKey = apiKey
            });
    }
});

Task("Default")
    .IsDependentOn("Push-Nuget-Package");

RunTarget(target);

private bool ShouldRunRelease() => AppVeyor.IsRunningOnAppVeyor && AppVeyor.Environment.Repository.Tag.IsTag;

private string GetPackageVersion()
{
    var gitVersion = GitVersion(new GitVersionSettings {
        RepositoryPath = "."
    });

    Information($"Git Semantic Version: {JsonConvert.SerializeObject(gitVersion)}");
    
    return gitVersion.NuGetVersionV2;
}

Nesse script adicionamos uma task de Setup responsável por limpar a pasta de artefatos. Essa task é executada automaticamente antes da task Default. Além de limpar a pasta de artefatos, você pode fazer qualquer ação inicial que desejar.

Também adicionamos uma condição nas tasks Create-Nuget-Package e Push-Nuget-Package para que executem apenas se o build foi disparado pela criação de uma Tag no repositório, e se estiver sendo executado no AppVeyor.

Na task Create-Nuget-Package, usamos o GitVersion para fazer o versionamento semântico de nosso pacote. Além disso, a tarefa Push-Nuget-Package também foi alterada para obter a chave de api do Nuget através da variável de ambiente NUGET_API_KEY, que foi criptografada anteriormente.

Observe que o método ShouldRunRelease usa o recurso de expression-body do C# 6.0. A versão 0.22 do Cake inclusive já é compatível com C# 7. Você pode conferir no release notes do Cake.

Deploy de um novo pacote Nuget

Conforme dito anteriormente, apesar de usarmos duas ferramentas de CI/CD, apenas o AppVeyor está responsável por fazer o deploy de nossos pacotes Nuget. Para isso, basta criar uma tag em nosso repositório.

git tag v1.0.3
git push origin --tags

Agora podemos nos preocupar apenas com o desenvolvimento de nosso componente, já que as ferramentas de CI vão garantir o build, execução de testes e deploy de nosso pacote Nuget.

Com isso, o processo de um novo build deve ser feito com sucesso. Confira os logs de execução do Travis e AppVeyor.

Você também pode verificar no nuget.org o pacote publicado.

Espero que tenham gostado, e se ficou alguma dúvida ou queiram dar alguma sugestão, por favor entrem em contato.

Um grande abraço e até a próxima!

Referências

Artigo pode ser lido também em:
https://static.imasters.com.br/wp-content/uploads/2019/01/10105324/Tratamento-global-de-Exceptions-no-ASP.Net-Core.jpg/