Unable to push nuGet packages to GitLab with dotnet CLI due to Unauthorized error

ibe*_*dev 7 nuget-package gitlab gitlab-ci dotnet-cli

GitLab now supports nuget public and private feed repository. I've got a public project (e.g: https://gitlab.com/sunnyatticsoftware/sasw-test-support) I create an access token for my user with api and write_repository (e.g: AAABBBCCCDDD)

I create a group variable in my CI/CD: SASW_API_ACCESS_TOKEN: AAABBBCCCDDD. All normal.

Then I create the multi stage CI/CD script to build, pack and publish. When attempting to publish the nuGet package with the following: dotnet nuget push **/*.nupkg --source https://gitlab.com/api/v4/projects/17141695/packages/nuget/index.json --api-key AAABBBCCCDDD --skip-duplicate

I get the error:

info : Pushing Sasw.TestSupport.2.0.2.nupkg to 'https://gitlab.com/api/v4/projects/17141695/packages/nuget'...
info :   PUT https://gitlab.com/api/v4/projects/17141695/packages/nuget/
info :   Unauthorized https://gitlab.com/api/v4/projects/17141695/packages/nuget/ 397ms
error: Response status code does not indicate success: 401 (Unauthorized).
ERROR: Job failed: exit code 1
Run Code Online (Sandbox Code Playgroud)

The documentation doesn't mention anything special, but I notice that when using the (legacy?) nuget CLI it passes a username. Dotnet CLI, however, doesn't support username, just API KEY.

Any idea why this is not working?

This is my CI/CD script:

variables:
  GITLAB_RUNNER_DOTNET_CORE: mcr.microsoft.com/dotnet/core/sdk:3.1
  NUGET_REPOSITORY: https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/nuget/index.json
  NUGET_API_KEY: $SASW_API_ACCESS_TOKEN
  NUGET_FOLDER_NAME: nupkgs
  NUGET_VERSION_SUFFIX: $SASW_PRERELEASE_SUFFIX

stages:
  - build
  - pack
  - release
  
#Docker image
image: $GITLAB_RUNNER_DOTNET_CORE

#Jobs
ci:
  stage: build
  script:
    - dotnet restore --no-cache --force
    - dotnet build --configuration Release --no-restore
    #- dotnet vstest test/*UnitTests/bin/Release/**/*UnitTests.dll
    #- dotnet vstest test/*IntegrationTests/bin/Release/**/*IntegrationTests.dll
    
pack-prerelease:
  stage: pack
  script:
    - dotnet pack *.sln --configuration Release --output $NUGET_FOLDER_NAME --version-suffix $NUGET_VERSION_SUFFIX --include-symbols -p:SymbolPackageFormat=snupkg
  artifacts:
    paths:
    - $NUGET_FOLDER_NAME
    expire_in: 1 week
  except:
    - master

pack-release:
  stage: pack
  script:
    - dotnet pack *.sln --configuration Release --output $NUGET_FOLDER_NAME
  artifacts:
    paths:
    - $NUGET_FOLDER_NAME
    expire_in: 1 week
  only:
    - master

publish-nuget:
  stage: release
  script:
    - dotnet nuget push **/*.nupkg --source $NUGET_REPOSITORY --api-key $NUGET_API_KEY --skip-duplicate
Run Code Online (Sandbox Code Playgroud)

PS: The project is public, so in case it's needed have a look at: https://gitlab.com/sunnyatticsoftware/sasw-test-support/-/jobs/451080235


UPDATE 1: Further verbosity from my local linux console

$ dotnet nuget -v Debug push **/*.nupkg --source https://gitlab.com/api/v4/projects/17141695/packages/nuget/index.json --api-key cBwt5_hidden_ --skip-duplicate
trace: NuGet Command Line Version: 5.4.0.2
info : Pushing Sasw.TestSupport.2.0.2.nupkg to 'https://gitlab.com/api/v4/projects/17141695/packages/nuget'...
info :   PUT https://gitlab.com/api/v4/projects/17141695/packages/nuget/
info :   Unauthorized https://gitlab.com/api/v4/projects/17141695/packages/nuget/ 1159ms
error: Response status code does not indicate success: 401 (Unauthorized).
trace: System.AggregateException: One or more errors occurred. (Response status code does not indicate success: 401 (Unauthorized).)
trace:  ---> System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
trace:    at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
trace:    at NuGet.Protocol.Core.Types.PackageUpdateResource.EnsureSuccessStatusCode(HttpResponseMessage response, Nullable`1 codeNotToThrow, ILogger logger)
trace:    at NuGet.Protocol.Core.Types.PackageUpdateResource.<>c__DisplayClass23_0.<PushPackageToServer>b__0(HttpResponseMessage response)
trace:    at NuGet.Protocol.HttpSource.ProcessResponseAsync[T](HttpSourceRequest request, Func`2 processAsync, SourceCacheContext cacheContext, ILogger log, CancellationToken token)
trace:    at NuGet.Protocol.Core.Types.PackageUpdateResource.PushPackageToServer(String source, String apiKey, String pathToPackage, Int64 packageSize, Boolean noServiceEndpoint, Boolean skipDuplicate, TimeSpan requestTimeout, ILogger logger, CancellationToken token)
trace:    at NuGet.Protocol.Core.Types.PackageUpdateResource.PushPackageCore(String source, String apiKey, String packageToPush, Boolean noServiceEndpoint, Boolean skipDuplicate, TimeSpan requestTimeout, ILogger log, CancellationToken token)
trace:    at NuGet.Protocol.Core.Types.PackageUpdateResource.PushPackage(String packagePath, String source, String apiKey, Boolean noServiceEndpoint, Boolean skipDuplicate, TimeSpan requestTimeout, ILogger log, CancellationToken token, Boolean isSnupkgPush)
trace:    at NuGet.Protocol.Core.Types.PackageUpdateResource.Push(String packagePath, String symbolSource, Int32 timeoutInSecond, Boolean disableBuffering, Func`2 getApiKey, Func`2 getSymbolApiKey, Boolean noServiceEndpoint, Boolean skipDuplicate, SymbolPackageUpdateResourceV3 symbolPackageUpdateResource, ILogger log)
trace:    at NuGet.Commands.PushRunner.Run(ISettings settings, IPackageSourceProvider sourceProvider, String packagePath, String source, String apiKey, String symbolSource, String symbolApiKey, Int32 timeoutSeconds, Boolean disableBuffering, Boolean noSymbols, Boolean noServiceEndpoint, Boolean skipDuplicate, ILogger logger)
trace:    at NuGet.CommandLine.XPlat.PushCommand.<>c__DisplayClass0_1.<<Register>b__1>d.MoveNext()
trace:    --- End of inner exception stack trace ---
trace:    at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
trace:    at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
trace:    at System.Threading.Tasks.Task`1.get_Result()
trace:    at Microsoft.Extensions.CommandLineUtils.CommandLineApplication.<>c__DisplayClass56_0.<OnExecute>b__0()
trace:    at Microsoft.Extensions.CommandLineUtils.CommandLineApplication.Execute(String[] args)
trace:    at NuGet.CommandLine.XPlat.Program.MainInternal(String[] args, CommandOutputLogger log)

Run Code Online (Sandbox Code Playgroud)

UPDATE 2: Alexey's answer is the proper one and up to date as per April 2021. GitLab has improved Nuget support in the last year and now it's possible to easily push packages to the repo package registry and have readacces on Nuget feed at project level or group level using deploy tokens.

Ale*_*rev 6

按照文档,推送从当前存储库构建的 NuGet 包非常容易。您不需要NuGet.config文件来推送包,因为可以为dotnet push命令指定凭据。您也不需要在 CI 文件中保留凭据,因为 CI 变量包含将包推送到项目包注册表所需的所有临时凭据。

这是我的工作.gitlab-ci.yml文件中的一个片段,我从文档中复制粘贴了它。所有必要的信息都来自 CI 变量,所以这个片段是完全可以重用的。

nuget:
  stage: deploy
  image: mcr.microsoft.com/dotnet/sdk:5.0-buster-slim
  script:
    - dotnet pack -c Release -o $PWD/nuget
    - dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text
    - dotnet nuget push "$PWD/nuget/*.nupkg" --source gitlab
  only:
    - master
    - tags
Run Code Online (Sandbox Code Playgroud)

关于安装包的问题,​​您确实需要一个令牌。但这不是您的个人访问令牌。要让人们从您的提要安装和恢复,您真正需要的是存储库部署令牌。如果组中有多个项目,其中包含多个发布了 NuGet 包的项目,则不必在项目级别定义它。您还可以为整个组创建部署令牌。您为此目的创建的部署令牌只需要具有read_package_registry权限,它不会向获得此令牌的用户授予任何其他权限。

创建部署令牌后,令牌名称用作用户名,令牌本身就是密码。您将这两者都放入NuGet.config文件中,其中列出了您的项目或小组提要。

例如:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <clear />
        <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
        <add key="myfeed" value="https://gitlab.mydomain.dev/api/v4/groups/19/-/packages/nuget/index.json" />
    </packageSources>
    <packageSourceCredentials>
        <myfeed>
            <add key="Username" value="gitlab+deploy-token-14" />
            <add key="ClearTextPassword" value="thetokenvalue" />
        </myfeed>
    </packageSourceCredentials>
</configuration>
Run Code Online (Sandbox Code Playgroud)


ibe*_*dev 5

我设法以一种完全不直观的方式推动了 nuget。我必须创建一个带有凭据详细信息的 NuGet.Config。这是我不喜欢的,因为将 api 密钥传递给dotnet nuget push命令就足够了。

无论如何,这些是我的步骤: dotnet new nugetconfig --force在根文件夹中为我的解决方案创建一个 NuGet.Config 然后编辑它以添加一个新源(链接到特定项目。烦人..)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <!--To inherit the global NuGet package sources remove the <clear/> line below -->
    <clear />
    <add key="nuget" value="https://api.nuget.org/v3/index.json" />
    <!--The feed URL line below. The key can be anything but it must match the section in packageSourceCredentials -->
    <add key="gitlab" value="https://gitlab.com/api/v4/projects/17141695/packages/nuget/index.json" />
  </packageSources>
  <packageSourceCredentials>
    <gitlab>
      <add key="Username" value="diegosasw" /> <!--My gitlab username-->
      <add key="ClearTextPassword" value="cBwt5HPD_hidden_" /> <!--My gitlab access key-->
    </gitlab>
  </packageSourceCredentials>
</configuration>
Run Code Online (Sandbox Code Playgroud)

然后以下命令将起作用

$ dotnet nuget push **/*.nupkg --source https://gitlab.com/api/v4/projects/17141695/packages/nuget/index.json --skip-duplicate
warn : No API Key was provided and no API Key could be found for 'https://gitlab.com/api/v4/projects/17141695/packages/nuget'. To save an API Key for a source use the 'setApiKey' command.
info : Pushing Sasw.TestSupport.2.0.3.nupkg to 'https://gitlab.com/api/v4/projects/17141695/packages/nuget'...
info :   PUT https://gitlab.com/api/v4/projects/17141695/packages/nuget/
info :   Created https://gitlab.com/api/v4/projects/17141695/packages/nuget/ 1936ms
info : Your package was pushed.
Run Code Online (Sandbox Code Playgroud)

现在我可以看到可用的包但没有元数据!(不知道为什么)。因此,如果我将相同的 NuGet.Config 添加到任何解决方案文件夹并尝试安装它可以工作的包。

$ dotnet add Foo/Foo.csproj package Sasw.TestSupport
  Writing C:\Users\dmsanz\AppData\Local\Temp\tmpF8D4.tmp
info : Adding PackageReference for package 'Sasw.TestSupport' into project 'Foo/Foo.csproj'.
info : Restoring packages for D:\src\sasw\sasw-test-support\Foo\Foo.csproj...
info :   CACHE https://api.nuget.org/v3-flatcontainer/sasw.testsupport/index.json
info :   CACHE https://gitlab.com/api/v4/projects/17141695/packages/nuget/download/sasw.testsupport/index.json
info : Package 'Sasw.TestSupport' is compatible with all the specified frameworks in project 'Foo/Foo.csproj'.
info : PackageReference for package 'Sasw.TestSupport' version '2.0.3' added to file 'D:\src\sasw\sasw-test-support\Foo\Foo.csproj'.
info : Committing restore...
info : Writing assets file to disk. Path: D:\src\sasw\sasw-test-support\Foo\obj\project.assets.json
log  : Restore completed in 1.13 sec for D:\src\sasw\sasw-test-support\Foo\Foo.csproj.
Run Code Online (Sandbox Code Playgroud)

它有效......但我根本不喜欢它。

  • 首先,我不能让我的 NuGet.Config 通用,他们需要有一个包含特定项目的 URL。至少有一个指向组而不是项目的 nuGet 提要会很好,这样 NuGet.Config 可以在不同的项目之间共享。
  • 使用 api-key 来运行命令并不直观,我需要委托 NuGet.Config 来提供身份验证。
  • 当我是创建项目的开发人员时,我喜欢在 NuGet.Config 中提供项目使用包所需NuGet 提要的详细信息。因此,如果根本需要任何凭据,这些将是只读凭据,以便其他人能够dotnet restore毫无问题地编译解决方案。但是在 GitLab 中使用这种方法,我需要NuGet.Config在我的解决方案文件夹中有一个包含敏感数据的敏感数据,例如具有写入权限的 api 密钥,以便能够从 CI/CD 管道推送包. 那应该是不能接受的。我是否被迫在我的 CI/CD 管道中做一些技巧来创建一个全新的 NuGet.Config 文件只是为了将敏感凭据放在那里以便发布 nuget 包,只是因为 GitLab 不支持一种好的和干净的方式来推送包到一个根本不需要 NuGet.Config 的简单命令的存储库?

我希望是错的。请如果有人有更好的解决方案,我很乐意将其标记为已接受的答案。