Home GitHub Packages: Migrate NuGet Packages Between GitHub Instances
Post
Cancel

GitHub Packages: Migrate NuGet Packages Between GitHub Instances

Overview

I recently had a customer ask me how they could migrate their NuGet packages from one GitHub instance to another (e.g.: from GitHub Enterprise Server to GitHub Enterprise Cloud). I wasn’t aware of any tooling that did this, so I decided to write my own.

The script

The repo and docs can be found here:

I decided to store the script in a separate GitHub repo than my github-misc-scripts repo to better facilitate any feedback/suggestions/improvements I might get - feel free to submit a PR if you can improve things 🚀!

Running the script

Prerequisites

  1. gh cli installed and logged in to be able to access the source GitHub instance:
    1
    
    gh auth login
    
  2. Auth to read packages from the source GitHub instance with gh:
    1
    
    gh auth refresh -h github.com -s read:packages # update -h with source github host
    
  3. gpr installed:
    1
    
    dotnet tool install gpr -g
    
  4. Can use this one-liner to find the absolute path for gpr for the <path-to-gpr> parameter:
    1
    
    find / -wholename "*tools/gpr" 2> /dev/null
    
  5. <target-pat> must have write:packages scope
  6. This assumes that the target org’s repo name is the same as the source.

We are passing gpr in as a parameter explicitly because sometimes gpr is aliased to git pull --rebase and that’s not what we want here.

Usage

You can call the script via:

1
2
3
4
5
6
./migrate-nuget-packages-between-orgs.sh \
  <source-org> 
  <source-host> \
  <target-org> \
  <target-pat> \
  <path-to-gpr>

Example

An example of this in practice:

1
2
3
4
5
6
./migrate-nuget-packages-between-orgs.sh \
  joshjohanning-org-packages \
  github.com \
  joshjohanning-org-packages-migrated \
  ghp_xyz \
  /home/codespace/.dotnet/tools/gpr

Use this one-liner to copy all .nupkg files to the current working directory before running the script:

1
find / -name "*.nupkg" -exec cp "{}" ./  \;

Notes

  • The script uses gpr to re-push the packages to the target org. It assumes that the target org’s repo has the same name as the source.
    • I initially tried writing this with dotnet nuget push, but that doesn’t seem to work since the package’s <RepositoryUrl> element would still be referencing the original repository. See error:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      dotnet nuget push \
        -s github \
        -k ghp_pat \
        NUnit3.DotNetNew.Template_1.7.1.nupkg
      
      Pushing NUnit3.DotNetNew.Template_1.7.1.nupkg to 'https://nuget.pkg.github.com/joshjohanning-org-packages-migrated'...
        PUT https://nuget.pkg.github.com/joshjohanning-org-packages-migrated/
      warn : Source owner 'joshjohanning-org-packages-migrated' does not match repo owner 'joshjohanning-org-packages' in repository element.
        BadRequest https://nuget.pkg.github.com/joshjohanning-org-packages-migrated/ 180ms
      error: Response status code does not indicate success: 400 (Bad Request).
      
    • gpr works because it rewrites the <repository url="..." /> element in the .nuspec file in the .nupkg before pushing
    • There is an item on GitHub’s roadmap to support pushing packages directly to an organization; this should allow dotnet nuget push to work instead of gpr
      • gpr still might be preferred since you would have to tie the NuGet package to the repository manually post-migration
      • For dotnet nuget push to work, you will have to add the feed first using this command:
        1
        2
        3
        4
        5
        6
        
        dotnet nuget add source \
          --username my-github-username \
          --password "ghp_pat" \
          --store-password-in-clear-text \
          --name github \
          "https://nuget.pkg.github.com/OWNER/index.json"
        
  • Also, in the script, I had to delete _rels/.rels and [Content_Types].xml because there was somehow two copies of each file in the package and it causes gpr to fail when extracting/zipping the package to re-push
  • To clean up the working directory when done, run this one-liner:
    1
    
    rm *.nupkg *.zip
    

Improvement Ideas

  • Add a source folder input instead of relying on current directory
  • Map between repositories where the target repo is named differently than the source repo
  • Dynamically determine out where gpr is installed instead of passing in a parameter (right now we are passing the gpr path in as a parameter explicitly because sometimes gpr is aliased to git pull --rebase)

Summary

Drop a comment here or an issue or PR on the repo if you have any feedback or suggestions! Happy packaging! 📦

This post is licensed under CC BY 4.0 by the author.

Using the GitHub Checks API to Link Workflow Statuses in a PR

GitHub Packages: Migrate NuGet Packages to GitHub Packages