Restoring packages from Azure Artifacts with Cake

Patrick Dinh
4 min readJun 27, 2019

--

Photo: https://pixabay.com/

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.

--

--

No responses yet