GitHub Packages: Migrate NuGet Packages Between GitHub Instances
Migrating NuGet packages stored in GitHub Packages from one instance to another
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.
See my other NuGet package migration posts:
See my other GitHub Package –> GitHub Package migration posts:
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
gh cli
installed- Set the source GitHub PAT env var:
export GH_SOURCE_PAT=ghp_abc
(must have at leastread:packages
,read:org
scope) - Set the target GitHub PAT env var:
export GH_TARGET_PAT=ghp_xyz
(must have at leastwrite:packages
,read:org
scope)
Notes:
- This script installs gpr locally to the
./temp/tools
directory - This script assumes that the target org’s repo name is the same as the source
- If the repo doesn’t exist, the package will still import but won’t be mapped to a repo
Usage
You can call the script via:
1
2
3
4
./migrate-nuget-packages-between-orgs.sh \
<source-org>
<source-host> \
<target-org>
Example
An example of this in practice:
1
2
3
4
5
6
7
export GH_SOURCE_PAT=ghp_abc
export GH_TARGET_PAT=ghp_xyz
./migrate-nuget-packages-between-orgs.sh \
joshjohanning-org-packages \
github.com \
joshjohanning-org-packages-migrated
Notes
- The script assumes that the target org’s repo has the same name as the source - it’s not required, but the package won’t be mapped to a repo if the target repo doesn’t exist
- The script uses
gpr
to re-push the packages to the target orgI 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- Update Dec 2022: Now that NuGet Packages has supports granular permissions and organization sharing (GitHub’s roadmap item),
dotnet nuget push
should work - but usinggpr
for mapping convenience gpr
still might be preferred since you would have to tie the NuGet package to the repository manually post-migration- If attempting to use
dotnet nuget push
, 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 -rf ./temp
Improvement Ideas
- Add a source folder input instead of relying on current directory (just using
./temp
) - Map between repositories where the target repo is named differently than the source repo (likely this isn’t needed since if repo doesn’t exist, packages will still be pushed, the package just won’t be linked to a repository)
- Dynamically determine out where
gpr
is installed instead of passing in a parameter (right now we are passing thegpr
path in as a parameter explicitly because sometimesgpr
is aliased togit pull --rebase
) (installinggpr
locally to the./temp/tools
directory) - Update script because of GitHub Packages GraphQL deprecation
Summary
Drop a comment here or an issue or PR on the repo if you have any feedback or suggestions! Happy packaging! 📦