PAYATHON 2026

project-scoped feeds 能否像 organization-scoped feeds 一样访问?

技术老齐

可以访问。project-scoped feed 同样支持 REST API 和 NuGet 客户端,但请求地址需要包含 feed 所属项目,调用身份也必须拥有该项目和 feed 的访问权限。

你给出的代码对所有 feed 都使用了 organization-scoped URL,需要先修正。Azure Artifacts 中的 promote 是将某个版本加入同一 feed 的 view,例如 @Release。跨 feed 发布则需要下载后再推送,或通过 upstream source 保存包。

1. 为 project-scoped feed 补上项目路径

列出 package 的请求地址分别为:

Organization-scoped feed:
https://feeds.dev.azure.com/{organization}/_apis/packaging/feeds/{feedId}/packages?api-version=7.1

Project-scoped feed:
https://feeds.dev.azure.com/{organization}/{project}/_apis/packaging/feeds/{feedId}/packages?api-version=7.1

{project} 是 feed 所属项目,可以使用项目 ID 或名称。即使使用 feed ID,project-scoped feed 的请求仍需包含项目参数。Get Packages API 文档

在现有 foreach ($feed in $bs_obj.value) 循环中,将构造 URL 的部分替换为:

$orgSegment = [Uri]::EscapeDataString($ORG)
$feedSegment = [Uri]::EscapeDataString([string]$feed.id)

$baseUrl = "https://feeds.dev.azure.com/$orgSegment"

if ($feed.project) {
    $projectSegment = [Uri]::EscapeDataString([string]$feed.project.id)
    $baseUrl += "/$projectSegment"
}

$url = "$baseUrl/_apis/packaging/feeds/$feedSegment/packages" +
       "?includeAllVersions=true&api-version=7.1"

$header = @{ Authorization = "Bearer $TOKEN" }
$param = @{
    Uri         = $url
    Headers     = $header
    ErrorAction = "Stop"
}

pipeline 遍历其他项目的 feed 时,应使用各个 $feed.project.id,不能统一使用 $(System.TeamProject)。

第一步保存 feed 列表时,也要提高 JSON 序列化深度,保留后续需要的嵌套属性:

$json = $result | ConvertTo-Json -Depth 20 -Compress

2. 检查实际使用的 Build Service 身份

能列出 feed,不代表后续 package 请求的路径和权限都正确。修正 URL 后若仍然失败,再检查调用身份的授权。

System.AccessToken 的权限取决于 job authorization scope 和对应 Build Service 账户的权限。常见身份有:

[Pipeline 项目名] Build Service ([Organization 名])
Project Collection Build Service ([Organization 名])

需要给实际使用的 Build Service 账户授权,只给自己的用户账户授权还不够。汇总表中的字符串 "Build Service" 也不能证明已确认实际调用身份。Job access token 文档

跨项目访问时,可以按以下顺序配置:

  1. 在 feed 所属项目的 Project settings > Permissions 中,将 pipeline 所属项目的 Build Service 加入 Readers。
  2. 在 Artifacts > Feed settings > Permissions 中,为相关构建身份分配所需角色。
  3. 按官方跨项目连接说明,同时检查项目级 Build Service 和 Project Collection Build Service 的 feed 权限。Feed scope 与跨项目访问说明

根据操作选择角色:

操作Feed 角色
列出、下载 feed 中已有的包Feed Reader
从 upstream source 首次安装并保存包Feed and Upstream Reader(Collaborator)
发布包、将版本 promote 到 viewFeed Publisher(Contributor)

源 feed 的读取权限和目标 feed 的发布权限需要分别配置。Feed 权限说明

3. 保留错误信息,修正汇总代码

当前 catch 将所有异常都记为 Visible = "NO",无法区分路径错误、认证失败和服务异常。至少应在原有 catch 中加入:

$statusCode = $null

if ($_.Exception.Response) {
    $statusCode = [int]$_.Exception.Response.StatusCode
}

Write-Warning (
    "Feed={0}; HTTP={1}; URL={2}; Error={3}" -f
    $feed.name, $statusCode, $url, $_.Exception.Message
)

if ($_.ErrorDetails.Message) {
    Write-Warning $_.ErrorDetails.Message
}

排查时,遇到 401 优先检查 token,403 优先检查授权,404 则要同时检查项目路径、feed 标识和资源可见性。现有输出没有保留这些信息,还不能确定问题只出在权限上。

第三段脚本还引用了所给代码中未定义的 $pat_obj。如果只是输出 Build Service 查询结果,可以直接遍历 $bs_obj:

foreach ($entry in @($bs_obj)) {
    Write-Host (
        "{0} | {1} | {2} | {3} | {4} | {5}" -f
        $entry.FeedName,
        $entry.Scope,
        $entry.Package,
        $entry.Version,
        $entry.Identity,
        $entry.Visible
    )
}

请求成功但返回空列表,与请求失败应分别记录。如果请求成功但缺少版本,再检查分页和筛选条件。includeAllVersions=true 仍会受到 NuGet 的 isRelease 等参数影响;package 较多时,还需要处理 $top、$skip。Get Packages 参数说明

4. 将稳定版本发布到 organization feed

如果采用“项目 feed 开发、组织 feed 统一发布”的布局,可以按以下流程发布:

  1. 确定经过验证的稳定版本。
  2. 从项目 feed 下载对应 .nupkg。
  3. 将该文件推送到 organization feed。
  4. 让各开发 feed 将 organization feed 配置为 upstream source。

NuGet 包的直接下载接口是:

GET https://pkgs.dev.azure.com/{organization}/{project}/_apis/packaging/feeds/{feedId}/nuget/packages/{packageName}/versions/{packageVersion}/content?api-version=7.1-preview.1

这个接口下载包文件,前面使用的 Get Packages 返回包的元数据。NuGet Download Package 文档

以下示例假设源 feed 已有经过验证的稳定版本 1.0.0,agent 已安装兼容的 .NET SDK,读取和发布权限也已配置。请将 SOURCE_PROJECT、SOURCE_FEED、TARGET_FEED 替换为实际值:

- task: NuGetAuthenticate@1

- pwsh: |
    $ErrorActionPreference = "Stop"

    $org = [Uri]::EscapeDataString($env:ORG)
    $project = [Uri]::EscapeDataString($env:SOURCE_PROJECT)
    $sourceFeed = [Uri]::EscapeDataString($env:SOURCE_FEED)
    $targetFeed = [Uri]::EscapeDataString($env:TARGET_FEED)

    $packageName = "my-package-name"
    $packageVersion = "1.0.0"

    $file = Join-Path $env:AGENT_TEMPDIRECTORY `
        "$packageName.$packageVersion.nupkg"

    $downloadUrl = "https://pkgs.dev.azure.com/$org/$project" +
        "/_apis/packaging/feeds/$sourceFeed/nuget/packages/$packageName" +
        "/versions/$packageVersion/content?api-version=7.1-preview.1"

    Invoke-WebRequest `
        -Uri $downloadUrl `
        -Headers @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } `
        -OutFile $file

    $targetUrl = "https://pkgs.dev.azure.com/$org" +
        "/_packaging/$targetFeed/nuget/v3/index.json"

    dotnet nuget push "$file" --source "$targetUrl" --api-key AzureArtifacts

    if ($LASTEXITCODE -ne 0) {
        throw "Package push failed: exit code $LASTEXITCODE"
    }
  displayName: "Publish stable package to organization feed"
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)
    ORG: $(ORG)
    SOURCE_PROJECT: $(SOURCE_PROJECT)
    SOURCE_FEED: $(SOURCE_FEED)
    TARGET_FEED: $(TARGET_FEED)

NuGetAuthenticate@1 用于配置 NuGet 客户端认证。--api-key AzureArtifacts 不是实际的访问 token,也不能替代 feed 授权。NuGetAuthenticate 使用说明

5. 只共享已验证版本,也可以使用 view

如果主要目的是控制下游可用的版本,可以在项目 feed 中将通过验证的版本 promote 到 @Release,再让 organization feed 将该项目 feed 的 @Release 配置为 upstream source。

添加 Azure Artifacts upstream 时,可以选择具体的 feed 和 view。配置完成后不会自动复制全部包;安装上游包时,才由 Collaborator 或更高角色触发保存副本。Upstream 配置说明

这种配置与“organization feed 作为开发 feed 的 upstream”方向不同,应根据实际依赖关系选择,避免形成循环。

发布审批状态和版本号也需要分开看:

  • promote 到 @Release 只改变版本所属的 view,不会把包移到其他 feed,也不会修改版本号。
  • 0.1.0-ci.117 即使进入 @Release,仍是 NuGet 预发布版本。需要正式版本时,应通过打包流程生成对应版本的包。
  • Azure Artifacts 的 promote 不支持 demote;目标 feed 中已发布的版本也不能通过覆盖或删除后重发来替换。Promote 与 view 说明、包版本不可变性说明

organization feed 中的 Microsoft.Extensions、System.Text.Json 等包,可能是从公共 upstream 安装后保存的依赖,仅凭包名无法确认来源。如果希望组织 feed 只保存内部发布包,还需要检查它配置的公共 upstream。

备注:内容仅供参考。