CI-CD com Travis e AppVeyor usando Cake e .Net Core
No artigo anterior usamos o Cake para fazer build, executar testes, gerar pacotes e deploy no repositorio oficial do Nuget através de um script de tarefas. Se você não viu corre lá e depois volte aqui.
Hoje iremos usar 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 1 dia e já não é mais válida. Lembre-se, essa chave foi utilizada no artigo anterior.
Calma!! Iremos corrigir isso mais adiante. Nesse 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 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
Iremos utilizar o AppVeyor para fazer deploy do nosso pacote Nuget, mas não podemos deixar a chave exposta conforme visto no artigo anterior. Iremos usar as variáveis de ambiente criptografadas no AppVeyor.
Para isso AppVeyor conta com a ferramenta Encrypt Configuration Data que nos permite criptografar qualquer informação em suas váriaveis 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.

Assim 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 à 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 tenham sugestões por favor entrem em contato.
Um grande abraço e até a próxima!