Restoring NuGet Packages from Artifactory into Azure DevOps

Jon Glass
4 min readJul 6, 2021

JFrog Artifactory is a market leader and a decent choice for package management. I’ve just completed a PoC which will hopefully allow us to retire an existing, self-hosted package server.

One hurdle we had to get over was finding a simple way to get Azure DevOps to pull packages from Artifactory as part of our build pipelines. JFrog provide an extension to help with this, but the documentation wasn’t super-easy to follow so here’s a run-through of how we’ve got it working:

Install the Extension

Log in to your Azure DevOps organisation and navigate to Organisation Settings >> Extensions. You may already see JFrog Artifactory in the list, in which case you’re all set:

If not, use Browse Marketplace to find and install this extension.

Add a Service Connection to your project

Now, navigate to your project and then Project Settings >> Service Connections >> New Service Connection, to add a new JFrog Artifactory service connection:

You need to enter the URL to your Artifactory instance as well as authentication details, which will look something like this:

The handy thing here is that this is the only place within your project where you need the authentication details, so as a project admin you can set this up and then anyone else can add or edit builds which pull packages using this connection.

Configure your build Pipeline to restore from Artifactory

With the service connection in place, you can now add (or update) the NuGet.config file in your solution root folder to look something like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add
key="Artifactory"
value="https://foo.jfrog.io/artifactory/api/nuget/foo-nuget" />
</packageSources>
</configuration>

Unfortunately though, the build step in your pipeline will probably fail at this point. We need to add a step before the build to restore packages from Artifactory, using the service connection. It’s easiest to do this using the interactive yaml editor in the portal first time around - once you have a pattern that works you can just copy the yaml snippet between builds. Start by finding the Artifactory NuGet task:

You’ll see two dropdowns in the dialog, which will automatically populate with available services and repos from Artifactory, which makes it very straightforward to set up:

Fun times with the Global Packages Folder

All good so far, right? If you’re really lucky your build might just work at this point - most likely if you’re building a Framework solution where the .sln file is in the repository root folder. Otherwise, chances are your build will fail. Fear not! I got stuck here for a good while but hopefully as a result I can explain what’s going on and how to fix it.

The yaml generated from adding the task above will look something like this:

- task: ArtifactoryNuGet@2
inputs:
command: 'restore'
artifactoryService: 'foo.jfrog.io'
targetResolveRepo: 'foo-nuget'
solutionPath: '**/*.sln'

This task fetches all your packages from Artifactory but it has to store them somewhere, and by default it puts them into a folder called packages in the root folder of your repository. If you get lucky (see above) then this is the right place for the build step to find them and pick them up, otherwise the build will just try (and fail) to restore packages for itself.

Framework projects use a packages folder in the solution root folder, but NuGet also caches downloaded packages in a global packages folder. Core dispenses with the solution-local packages folder entirely and just uses the global one. So, the best thing to have Artifactory do is to put the downloaded packages into the global packages folder, and the safest way to do that is to override this folder location by setting the NUGET_PACKAGESenvironment variable. Before your build steps, add this variable to your yaml file:

variables:
NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages

You’ve now overridden the global packages folder location and the last thing that you need to do is to tell the Artifactory task to download packages to this folder, so that your builds will will able to find them - see the extra line added to the task below:

- task: ArtifactoryNuGet@2
inputs:
command: 'restore'
artifactoryService: 'foo.jfrog.io'
targetResolveRepo: 'foo-nuget'
solutionPath: '**/*.sln'
packagesDirectory: '$(NUGET_PACKAGES)'

Your build should work now, but let me know if you have any further difficulties!

Caching for improved build performance

Since each new build is run, by design, in a clean environment, this means that every time your run your pipeline it’s going to pull all of its packages from Artifactory. You can make this a lot faster and save on bandwidth by using a Pipeline Cache step, which is the subject of my next post…

--

--