NuGet Packages
  • 13 Apr 2022
  • 9 Minutes to read
  • Dark
    Light
  • PDF

NuGet Packages

  • Dark
    Light
  • PDF

CI/CD for NuGet Packages

NuGet is the packaging platform used with .NET and provides developers easy access to NuGet.org, a public package gallery with thousands of free and open source packages. By discovering and incorporating these code packages into in their own applications, developers can save them time by not solving the same problem over and over. In addition, NuGet packages are much more manageable and less error-prone than "copy/pasting" code found on the Internet. They have dependency management built-in, and developers can easily find out when new version of packages are published (bug fixes, security bugs) and rebuild their application using the new version of the packages.

Although NuGet was originally designed for free and open source packages hosted at NuGet.org, with a private package repository like ProGet, you can host your own packages without making them public. This gives you the same benefits of code reuse, code sharing, within your organization. By using BuildMaster and ProGet's advanced features, you can apply Continuous Integration & Continuous Delivery (CI/CD) principles to rapidly test and deliver these packages alongside your application.

See it live! The NuGet CI/CD Template is available as an application template in your own instance of BuildMaster.

NuGet Package CI/CD Pipeline

CI/CD pipelines for NuGet packages are similar to CI/CD pipelines for applications: a Build Trigger monitors source control and automatically creates a build that enters a deployment pipeline. However, instead of creating build artifacts at the first stage and deploying those artifacts to each environment, you will create and publish a pre-release package, publish it to a package source, then repackage at each stage.

  • Build: create a pre-release Continuous Integration (CI) version of a package (e.g. ProfitCalc v3.2.4-ci.11) and publish to your package source (i.e. ProGet feed)
  • Test: instruct ProGet to repackage that version into a more stable, Release Candidate (RC) version (e.g. ProfitCalc-3.2.4-rc.11)
  • Release: instruct ProGet to repackage the RC version to a stable version (e.g. ProfitCalc-3.2.4)

If you have a lot of CI packages due to a lot of commits, you should consider publishing those packages to a different feed (a CI feed, for example), and instructing ProGet to promote the repackaged packages.

NuGet Package CI/CD Development Workflow

Teams who are new to building and using their own NuGet packages are often frustrated by the additional steps required in separating code across multiple projects. However, this package CI/CD workflow can greatly simplify package development and use for developers.

  1. Develop and test package changes locally (i.e. on workstation)
  2. Commit a change to a package's repository then wait for CI package to be created
  3. Upgrade application to use CI package (locally), then make appropriate changes and test (locally)
  4. Promote package from CI Build to Test stage in BuildMaster
  5. Upgrade application to use RC package, then commit changes
  6. Proceed as per any other application change

The RC package can be promoted to a stable package at anytime, and other developers can download and use RC packages as needed. And because all of this is managed as a pipeline in BuildMaster, everyone has visibility into which stages each package is in.

However, you should still never release an application that uses unstable (pre-release) packages. This means:

  • publish packages to production before your application is promoted
  • can use an automatic to ensure that applications don't use pre-release packages

Creating a Package Version Number

NuGet packages uses the semantic version format, which is a 3-part number (MAJOR.MINOR.PATCH) that determines how backwards- and forwards-compatible changes will be. Before building a NuGet package, you will need this version number to create the package.

This is where BuildMaster comes in: it's the source of truth for build and release numbers, which means you simply use those to generate your package version number. There's no need to commit this version number to source control, and doing so will actually make it harder to keep these numbers in sync.

The easiest way to create a package version number is by comparing the release and build numbers in an OtterScript expression:

set $packageVersion = $ReleaseNumber-CI.$BuildNumber;

You can then use this variable in subsequent operations in your OtterScript.

Anti-pattern: Directly Building Stable Packages
You should never build a stable (i.e. non-prerelease) package. Always build a prerelease package, then let BuildMaster and ProGet promote and repackage.

When first developing a package for the first time, you should use - worth adding version 0.y.z should be used in development

Building NuGet Packages

A NuGet package is a ZIP file that contains a package manifest file and compiled code (DLLs). Microsoft supports three different methods to create NuGet packages, all of which you can do with operations in BuildMaster:

  • dotnet CLI uses the DotNet::build operation
  • nuget.exe CLI uses the NuGet::Create-Package operation
  • MSBuild uses the MSBuild::Build-Project operation

All of these methods and operations will ultimately create the same package, but before doing that make sure to create and set a version number.

For the NuGet CI/CD template application, we use the DotNet::Set-ProjectVersion to edit the project file, and then the DotNet::Build operation to build the code and create the package.

DotNet::Set-ProjectVersion
(
    FromDirectory: ~\Src\$PackageName,
    Version: $ReleaseNumber,
    AssemblyVersion: $ReleaseNumber.0,
    FileVersion: $ReleaseNumber.$BuildNumber,
    PackageVersion: $ReleaseNumber-CI.$BuildNumber
);

DotNet::Build ~\Src\$PackageName
(
    Configuration: Release
);

Other methods utilize a "nuspec" file to be created (nuget.exe spec), which you similarly edit using the `Replace-Text operation.

Publishing and Attaching

Instead of capturing the build output as an artifact, you can use the NuGet::PublishPackage operation to publish it to a package source that you've configured:

NuGet::Publish-Package ~\Src\$PackageName\bin\Release\$PackageName.$packageVersion.nupkg
(
    Source: CIPackages,
    AttachToBuild: true
);

A package source is essentially a ProGet feed URL with a resource credential (username/password).

The second parameter (AttachToBuild) indicates that a package reference will be associated with the build. Think of this as a pointer of source to the that was published; you can easily navigate to it from the UI, use it later in repackaging operations.

Publishing to NuGet.org

To publish packages to NuGet.org, you first need to create an account. Once you have a NuGet account, you can publish packages to NuGet.org from BuildMaster by simply creating resource credentials pointing to NuGet.org.

Repackaging NuGet Packages

Repackaging is used to create a new package from an existing package using exactly the same, verified content while maintaining the integrity of the original package and providing an audit trail to show when and why the repackaging occurred.

You can use the ProGet::Repack-Package operation to instruct your ProGet server to perform to repackage:

ProGet::Repack-Package
(
    Name: ProfitCalc,
    NewVersion: $PackageVersion(ProfitCalc, stable)-RC.$BuildNumber
);

In this case, the ProfitCalc package is already attached to the build from an earlier Publish-Package, which means BuildMaster can simply use the referenced package application to publish.

HOWTO: Step-by-step for BuildMaster and ProGet

This requires a ProGet instance, a BuildMaster instance, and the .NET SDK installed .

Step 1: Navigate to a NuGet Feed in ProGet

Whether you create a new feed or use an existing one, navigate to the desired NuGet feed in ProGet.

On the feed page, copy the API URL from the top right corner.

gettingstarted_proget_feedapi.png

This API will be used in Step 6.

Step 2: Navigate to API Settings

Navigate to ProGet’s settings via the gear icon and select "API Keys" under the "Integrations & Extensibility" section.

Step 3: Create an API Key

In the API Settings, click [Create API Key] and a pop-up will appear.

In the pop-up window choose the Key Type (for this tutorial, choose "Feed"), select the feed you want to connect (we named a NuGet feed 'tutorial' for this guide), and select the package permissions.

Package permissions will vary depending on your organization's preferences.

gettingstarted_proget_feedapiconfig.png

Leave the other settings at their default and click [Save API Key].

Step 4: Build a New Application in BuildMaster

In a BuildMaster instance, create a new NuGet application.

gettingstarted_buildmaster_createnugetapp.png

The page will automatically go to the application overview.

Step 5: Create a Secure Resource

After the template is applied, you’ll need to create a secure resource to clear the first warning shield.

From the overview page, hover over [Settings] and click "Resources."

On the Resource page, click [Create Secure Resource] on the bottom right corner.

gettingstarted_buildmaster_createsecure.png

In the pop-up, select "NuGet Package Feed" from the list of available options.

Name the resource as appropriate.

On the next screen, select the "create..." buttom at the bottom of the pop-up.

gettingstarted_buildmaster_createsecure2.png

On the "Select Secure Credentials Type" page of the pop-up, select "API Key/Token."

In the "Select Secure Credentials Type" select "API Key/Token.

Step 6: Create an API Key Within Secure Credentials

In the "Create Secure Credential" API Window, name the API and leave the remaining items on the default.

gettingstarted_buildmaster_createsecurecred.png

Save the credential and the page will return to the "Create Secure Resource" page.

Paste the API Key from the NuGet Feed in Step 1.

From the Credentials drop down, select the Credential just created.

gettingstarted_buildmaster_createsecureresource_highlighted.png

Click [Save Resource].

Step 7: Edit Package Name in BuildMaster

Navigate back to the top of the Application by click [Overview].

On the overview page, click [Package Name] from the list of settings. This will help clear the warning shields.

In the "Edit Variable" pop-up, input the name of the secure resource just created and add a desired package's name.

gettingstarted_buildmaster_packagename.png

Step 8: Confirm CI/CD Connection between ProGet and BuildMaster

Your NuGet CI/CD pipeline is now ready to use, so create a new build in BuildMaster. You’ll be prompted to create a release, and then a new build.

gettingstarted_buildmaster_buildapp.png

If you’ve configured everything correctly, when you go back to the ProGet feed, you’ll see a new package with -ci ending in the ProGet feed.

ProGet clearly indicates that it’s a prerelease version, and you can view the metadata, files, and more by clicking the package name.

As you deploy your package through this NuGet CI/CD pipeline, BuildMaster will continue to push pre-release and finally the stable version of your package to ProGet.

gettingstarted_buildmaster_deploy.png

Prevent the Release of Applications with Unstable Package References

Unstable (pre-release) packages are important to use when testing a package's code prior to its release, but by definition they're not suitable for release. As such, you should not deploy applications that reference the pre-release packages you create.

BuildMaster can help with this by performing an automated check against a variable that you set your application's build process using the DotNet::Get-Dependencies operation.

Inedo's Use Case

To prevent us from accidentally releasing a product (such as Otter) with a pre-release version of one of our libraries (like InedoLib), we use the following OtterScript in our build plan to set the $UnstableDependencies variable, and then we test that variable prior to deploying to the Release stage.

set %prereleasePackages = %();

foreach $projPath in @FileMask(**.csproj)
{
    DotNet::Get-Dependencies
    (
        ProjectPath: $projPath,
        Dependencies => %nugets
    );

    foreach $k in @MapKeys(%nugets)
    {
        set $v = %nugets[$k];

        if $MatchesRegex($v, -)
        {
            set %prereleasePackages[$k] = $v;
        }
    }
}

set $buildVar = none;

set @preList = @();

foreach $k in @MapKeys(%prereleasePackages)
{
    set @preList = $ListInsert(@preList, $k $(%prereleasePackages[$k]));
}

if $ListCount(@preList) != 0
{
    set $buildVar = $Join(', ', @preList);
}

Set-BuildVariable UnstableDependencies
(
    Value: $buildVar
);

Was this article helpful?