Restoring packages from Azure Artifacts with Cake
What is Cake?
Cake (C# Make) is a cross-platform build automation system for C#. Many developers love Cake because of its reliability. A Cake script creates the same output regardless of whether it’s run locally or on a CI system, for example TeamCity, AppVeyor or Azure DevOps.
The problem
Recently I set up an Azure DevOps CI for a .NET Core project. This time I chose to go with Cake. The reasons are:
- Cake gives me better control over the build artifacts.
- I can be fairly certain that once the build works on my machine, it should behave the same way in Azure DevOps.
The whole set up is quite simple:
- In [build.cake]
// Restore Nuget packages
DotNetCoreRestore("./src/");// Build the solution
DotNetCoreBuild("./src/");// Publish the build output
- In [build.ps1]
// Invoke cake
Invoke-Expression "& `".\tools\Cake\Cake.exe`" `"build.cake`""
Cake runs happily on my machine but when it runs on Azure DevOps, I received
error NU1101: Unable to find package MyPersonal.AwesomePackage. No packages exist with this id in source(s): Microsoft Visual Studio Offline Packages, nuget.org
Turns out my second assumption isn’t completely correct. What happened was [MyPersonal.AwesomePackage] is hosted in a private Azure Artifacts and Nuget couldn’t find it.
The solutions
Use the built-in Nuget Restore task
This involves adding a build task then specify the private Azure Artifact feed to be included.
While this solution requires the least messing around. However, I would like to find something else that would work in Cake entirely. I think it’s handy to be able to run the Cake script on developer machines as well.
Configure Nuget.config with Cake
I found this issue on GitHub. The proposed solution is to configure [Nuget.config] with Cake.
private void AddAzureArtifactsAsAuthenticatedNuGetSource(){
var source = new {
Name = "PrivateAzureFeed",
Source = "https://uri-to-private-azure-feed",
ApiUserName = "VSTS", //dont-care value
ApiKey = EnvironmentVariable("AZUREDEVOPS_PAT")
}; if (NuGetHasSource(source.Source))
{
NuGetRemoveSource(source.Name, source.Source);
}
NuGetAddSource(
source.Name,
source.Source,
new NuGetSourcesSettings {
UserName = source.ApiUserName,
Password = source.ApiKey,
IsSensitiveSource = false,
Verbosity = NuGetVerbosity.Detailed
}
);
NuGetSetApiKey(source.ApiKey, source.Source);
}
Call [AddAzureArtifactsAsAuthenticatedNuGetSource] before [DotNetCoreRestore] and we should be good.
Here, I run into another road block. I need to find the ApiKey to access the private Azure Artifacts feed. Microsoft document says that the ApiKey can be a personal access token.
With some more digging, I found an Azure DevOps predefined variable [System.AccessToken].
- It is generated in the build agent. Therefore, no need to maintain a personal access token or configuring any environment variable.
- It acts as the ApiKey to access the private Azure Artifacts feed. Important: it only works if the CI and the Artifacts are located at the same Azure DevOps organization.
- Most importantly, it can be called from PowerShell as [$env:SYSTEM_ACCESSTOKEN]. This is pretty cool for me because I can configure the variable with my personal access token locally and Nuget restore would work the same way.
I go ahead and update the [build.ps1] with the newly found variable. The whole solution is slightly bigger.
My [build.cake] looks like this
// Read the access token from the params
var accessToken = Argument<string>("accessToken", "none");// Add Azure Artifacts to Nuget.config
AddAzureArtifactsAsAuthenticatedNuGetSource();// Restore Nuget packages
DotNetCoreRestore("./src/");// Build the solution
DotNetCoreBuild("./src/");private void AddAzureArtifactsAsAuthenticatedNuGetSource(){
var source = new {
Name = "PrivateAzureFeed",
Source = "https://uri-to-private-azure-feed",
ApiUserName = "VSTS", //dont-care value
ApiKey = accessToken // pass the accessToken in
}; if (NuGetHasSource(source.Source))
{
NuGetRemoveSource(source.Name, source.Source);
}
NuGetAddSource(
source.Name,
source.Source,
new NuGetSourcesSettings {
UserName = source.ApiUserName,
Password = source.ApiKey,
IsSensitiveSource = false,
Verbosity = NuGetVerbosity.Detailed
}
);
NuGetSetApiKey(source.ApiKey, source.Source);
}
My [build.ps1] looks like this
// Invoke cake
Invoke-Expression "& `".\tools\Cake\Cake.exe`" `"build.cake`"" -accessToken=`"$env:SYSTEM_ACCESSTOKEN`"
Conclusion
Cake is great! It’s cross-platform. It produces the same output regardless of the environments (kind of). We can configure a private Azure Artifacts as a Nuget source with Cake. If the Azure Artifacts is on the same organization with the CI pipeline, we even can use the built-in [System.AccessToken] as the ApiKey.