由于未授权错误,无法使用 dotnet CLI 将 nuGet 包推送到 GitLab

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

GitLab 现在支持 nuget public 和私有提要存储库。 我有一个 public 项目(例如:https://gitlab.com/sunnyatticsoftware/sasw-test-support) 我使用 apiwrite_repository(例如:AAABBBCCCDDD)

为我的用户创建了一个访问令牌

我在 CI/CD 中创建了一个组变量:SASW_API_ACCESS_TOKENAAABBBCCCDDD。一切正常。

然后我创建多阶段 CI/CD 脚本来构建、打包和发布。 尝试使用以下内容发布 nuGet 包时: dotnet nuget push **/*.nupkg --source https://gitlab.com/api/v4/projects/17141695/packages/nuget/index.json --api-key AAABBBCCCDDD --skip-duplicate

我收到错误:

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

documentation 没有提到任何特别之处,但我注意到在使用(旧版?)nuget CLI 时它会传递一个用户名。但是,Dotnet CLI 不支持用户名,仅支持 API KEY。

知道为什么这不起作用吗?

这是我的 CI/CD 脚本:

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

PS:该项目是public,所以如果需要,请查看:https://gitlab.com/sunnyatticsoftware/sasw-test-support/-/jobs/451080235


更新 1:来自我本地 linux 控制台的更多详细信息

$ 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)

更新 2:Alexey 的回答是正确的,并且截至 2021 年 4 月是最新的。GitLab 在去年改进了 Nuget 支持,现在可以轻松地将包推送到 repo 包注册表并在 Nuget 上进行读取使用部署令牌在项目级别或组级别提供信息。

我设法以一种根本不直观的方式推送 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>

然后下面的命令就可以工作了

$ 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.

现在我可以看到包可用但没有元数据!(不知道为什么)。因此,如果我将完全相同的 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.

它有效...但我一点也不喜欢它。

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

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

按照 documentation 推送从当前存储库构建的 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

关于安装包的问题,​​确实需要token。但它不是您的个人访问令牌。要让人们从您的 Feed 安装和恢复,您真正需要的只是 repository deploy token。如果您在一个组中有多个项目,并且包含多个发布了 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>

一如既往,我阅读以上所有答案的速度太快了。所以,希望能帮助别人:

我的解决方案是使用 DEPLOY TOKENS 而不是“正常”访问令牌。

在此处阅读更多内容:https://docs.gitlab.com/ee/user/project/deploy_tokens/