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!