From bfc7891c172503eb3534338c7d49db391b595a8b Mon Sep 17 00:00:00 2001 From: ray chen Date: Mon, 13 Oct 2025 18:35:43 +0000 Subject: [PATCH 1/3] Update create-apireview template to use packageInfo --- .../templates/steps/create-apireview.yml | 52 +++-- eng/common/scripts/Create-APIReview.ps1 | 177 ++++++++++++------ 2 files changed, 152 insertions(+), 77 deletions(-) diff --git a/eng/common/pipelines/templates/steps/create-apireview.yml b/eng/common/pipelines/templates/steps/create-apireview.yml index 124e027308f4..6942abd0baa6 100644 --- a/eng/common/pipelines/templates/steps/create-apireview.yml +++ b/eng/common/pipelines/templates/steps/create-apireview.yml @@ -1,27 +1,47 @@ parameters: - ArtifactPath: $(Build.ArtifactStagingDirectory) - Artifacts: [] - ConfigFileDir: $(Build.ArtifactStagingDirectory)/PackageInfo - MarkPackageAsShipped: false - GenerateApiReviewForManualOnly: false - ArtifactName: 'packages' - PackageName: '' - SourceRootPath: $(Build.SourcesDirectory) + - name: ArtifactPath + type: string + default: $(Build.ArtifactStagingDirectory) + - name: Artifacts + type: object + default: [] + - name: ConfigFileDir + type: string + default: $(Build.ArtifactStagingDirectory)/PackageInfo + - name: MarkPackageAsShipped + type: boolean + default: false + - name: GenerateApiReviewForManualOnly + type: boolean + default: false + - name: ArtifactName + type: string + default: 'packages' + - name: PackageName + type: string + default: '' + - name: SourceRootPath + type: string + default: $(Build.SourcesDirectory) + - name: PackageInfoFiles + type: object + default: [] steps: - # ideally this should be done as initial step of a job in caller template - # We can remove this step later once it is added in caller - - template: /eng/common/pipelines/templates/steps/set-default-branch.yml - parameters: - WorkingDirectory: ${{ parameters.SourceRootPath }} - # Automatic API review is generated for a package when pipeline runs irrespective of how pipeline gets triggered. - # Below condition ensures that API review is generated only for manual pipeline runs when flag GenerateApiReviewForManualOnly is set to true. + # Below condition ensures that API review is generated only for manual pipeline runs when flag GenerateApiReviewForManualOnly is set to true. - ${{ if or(ne(parameters.GenerateApiReviewForManualOnly, true), eq(variables['Build.Reason'], 'Manual')) }}: + # ideally this should be done as initial step of a job in caller template + # We can remove this step later once it is added in caller + - template: /eng/common/pipelines/templates/steps/set-default-branch.yml + parameters: + WorkingDirectory: ${{ parameters.SourceRootPath }} + - task: Powershell@2 inputs: filePath: ${{ parameters.SourceRootPath }}/eng/common/scripts/Create-APIReview.ps1 arguments: > + -PackageInfoFiles ('${{ convertToJson(parameters.PackageInfoFiles) }}' | ConvertFrom-Json -NoEnumerate) -ArtifactList ('${{ convertToJson(parameters.Artifacts) }}' | ConvertFrom-Json | Select-Object Name) -ArtifactPath '${{parameters.ArtifactPath}}' -ArtifactName ${{ parameters.ArtifactName }} @@ -31,7 +51,7 @@ steps: -DefaultBranch '$(DefaultBranch)' -ConfigFileDir '${{parameters.ConfigFileDir}}' -BuildId '$(Build.BuildId)' - -RepoName '$(Build.Repository.Name)' + -RepoName '$(Build.Repository.Name)' -MarkPackageAsShipped $${{parameters.MarkPackageAsShipped}} pwsh: true displayName: Create API Review diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index 5438df067101..a4b4d401e9e1 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -1,11 +1,11 @@ [CmdletBinding()] Param ( - [Parameter(Mandatory=$True)] + [Parameter(Mandatory=$False)] [array] $ArtifactList, [Parameter(Mandatory=$True)] - [string] $ArtifactPath, + [string] $ArtifactPath, [Parameter(Mandatory=$True)] - [string] $APIKey, + [string] $APIKey, [string] $SourceBranch, [string] $DefaultBranch, [string] $RepoName, @@ -14,7 +14,9 @@ Param ( [string] $ConfigFileDir = "", [string] $APIViewUri = "https://apiview.dev/AutoReview", [string] $ArtifactName = "packages", - [bool] $MarkPackageAsShipped = $false + [bool] $MarkPackageAsShipped = $false, + [Parameter(Mandatory=$False)] + [array] $PackageInfoFiles ) Set-StrictMode -Version 3 @@ -51,7 +53,7 @@ function Upload-SourceArtifact($filePath, $apiLabel, $releaseStatus, $packageVer $versionContent.Headers.ContentDisposition = $versionParam $multipartContent.Add($versionContent) Write-Host "Request param, packageVersion: $packageVersion" - + $releaseTagParam = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data") $releaseTagParam.Name = "setReleaseTag" $releaseTagParamContent = [System.Net.Http.StringContent]::new($MarkPackageAsShipped) @@ -96,7 +98,7 @@ function Upload-ReviewTokenFile($packageName, $apiLabel, $releaseStatus, $review $fileName = Split-Path -Leaf $filePath Write-Host "OriginalFile name: $fileName" - $params = "buildId=${BuildId}&artifactName=${ArtifactName}&originalFilePath=${fileName}&reviewFilePath=${reviewFileName}" + $params = "buildId=${BuildId}&artifactName=${ArtifactName}&originalFilePath=${fileName}&reviewFilePath=${reviewFileName}" $params += "&label=${apiLabel}&repoName=${RepoName}&packageName=${packageName}&project=internal&packageVersion=${packageVersion}" if($MarkPackageAsShipped) { $params += "&setReleaseTag=true" @@ -141,17 +143,16 @@ function Get-APITokenFileName($packageName) } } -function Submit-APIReview($packageInfo, $packagePath, $packageArtifactName) +function Submit-APIReview($packageInfo, $packagePath) { - $packageName = $packageInfo.Name $apiLabel = "Source Branch:${SourceBranch}" # Get generated review token file if present # APIView processes request using different API if token file is already generated - $reviewTokenFileName = Get-APITokenFileName $packageArtifactName + $reviewTokenFileName = Get-APITokenFileName $packageInfo.ArtifactName if ($reviewTokenFileName) { Write-Host "Uploading review token file $reviewTokenFileName to APIView." - return Upload-ReviewTokenFile $packageArtifactName $apiLabel $packageInfo.ReleaseStatus $reviewTokenFileName $packageInfo.Version $packagePath + return Upload-ReviewTokenFile $packageInfo.ArtifactName $apiLabel $packageInfo.ReleaseStatus $reviewTokenFileName $packageInfo.Version $packagePath } else { Write-Host "Uploading $packagePath to APIView." @@ -172,12 +173,32 @@ function IsApiviewStatusCheckRequired($packageInfo) return $false } -function ProcessPackage($packageName) +function ProcessPackage($packageInfo) { $packages = @{} if ($FindArtifactForApiReviewFn -and (Test-Path "Function:$FindArtifactForApiReviewFn")) { - $packages = &$FindArtifactForApiReviewFn $ArtifactPath $packageName + $pkgArtifactName = $packageInfo.ArtifactName ?? $packageInfo.Name + + # Check if the function supports the packageInfo parameter + $functionInfo = Get-Command $FindArtifactForApiReviewFn -ErrorAction SilentlyContinue + $supportsPackageInfoParam = $false + + if ($functionInfo -and $functionInfo.Parameters) { + # Check if function specifically supports packageInfo parameter + $parameterNames = $functionInfo.Parameters.Keys + $supportsPackageInfoParam = $parameterNames -contains 'packageInfo' + } + + # Call function with appropriate parameters + if ($supportsPackageInfoParam) { + LogInfo "Calling $FindArtifactForApiReviewFn with packageInfo parameter" + $packages = &$FindArtifactForApiReviewFn $ArtifactPath $pkgArtifactName $packageInfo + } + else { + LogInfo "Calling $FindArtifactForApiReviewFn with legacy parameters" + $packages = &$FindArtifactForApiReviewFn $ArtifactPath $pkgArtifactName + } } else { @@ -192,31 +213,23 @@ function ProcessPackage($packageName) foreach($pkgPath in $packages.Values) { $pkg = Split-Path -Leaf $pkgPath - $pkgPropPath = Join-Path -Path $ConfigFileDir "$packageName.json" - if (-Not (Test-Path $pkgPropPath)) - { - Write-Host " Package property file path $($pkgPropPath) is invalid." - continue - } - # Get package info from json file created before updating version to daily dev - $pkgInfo = Get-Content $pkgPropPath | ConvertFrom-Json - $version = [AzureEngSemanticVersion]::ParseVersionString($pkgInfo.Version) - if ($version -eq $null) + $version = [AzureEngSemanticVersion]::ParseVersionString($packageInfo.Version) + if ($null -eq $version) { - Write-Host "Version info is not available for package $packageName, because version '$(pkgInfo.Version)' is invalid. Please check if the version follows Azure SDK package versioning guidelines." + Write-Host "Version info is not available for package $($packageInfo.ArtifactName), because version '$($packageInfo.Version)' is invalid. Please check if the version follows Azure SDK package versioning guidelines." return 1 } - + Write-Host "Version: $($version)" - Write-Host "SDK Type: $($pkgInfo.SdkType)" - Write-Host "Release Status: $($pkgInfo.ReleaseStatus)" + Write-Host "SDK Type: $($packageInfo.SdkType)" + Write-Host "Release Status: $($packageInfo.ReleaseStatus)" # Run create review step only if build is triggered from main branch or if version is GA. # This is to avoid invalidating review status by a build triggered from feature branch if ( ($SourceBranch -eq $DefaultBranch) -or (-not $version.IsPrerelease) -or $MarkPackageAsShipped) { Write-Host "Submitting API Review request for package $($pkg), File path: $($pkgPath)" - $respCode = Submit-APIReview $pkgInfo $pkgPath $packageName + $respCode = Submit-APIReview $packageInfo $pkgPath Write-Host "HTTP Response code: $($respCode)" # no need to check API review status when marking a package as shipped @@ -224,10 +237,10 @@ function ProcessPackage($packageName) { if ($respCode -eq '500') { - Write-Host "Failed to mark package ${packageName} as released. Please reach out to Azure SDK engineering systems on teams channel." + Write-Host "Failed to mark package $($packageInfo.ArtifactName) as released. Please reach out to Azure SDK engineering systems on teams channel." return 1 } - Write-Host "Package ${packageName} is marked as released." + Write-Host "Package $($packageInfo.ArtifactName) is marked as released." return 0 } @@ -239,41 +252,41 @@ function ProcessPackage($packageName) IsApproved = $false Details = "" } - Process-ReviewStatusCode $respCode $packageName $apiStatus $pkgNameStatus + Process-ReviewStatusCode $respCode $packageInfo.ArtifactName $apiStatus $pkgNameStatus if ($apiStatus.IsApproved) { Write-Host "API status: $($apiStatus.Details)" } - elseif (!$pkgInfo.ReleaseStatus -or $pkgInfo.ReleaseStatus -eq "Unreleased") { + elseif (!$packageInfo.ReleaseStatus -or $packageInfo.ReleaseStatus -eq "Unreleased") { Write-Host "Release date is not set for current version in change log file for package. Ignoring API review approval status since package is not yet ready for release." } elseif ($version.IsPrerelease) { # Check if package name is approved. Preview version cannot be released without package name approval - if (!$pkgNameStatus.IsApproved) + if (!$pkgNameStatus.IsApproved) { - if (IsApiviewStatusCheckRequired $pkgInfo) + if (IsApiviewStatusCheckRequired $packageInfo) { Write-Error $($pkgNameStatus.Details) return 1 } else{ - Write-Host "Package name is not approved for package $($packageName), however it is not required for this package type so it can still be released without API review approval." - } + Write-Host "Package name is not approved for package $($packageInfo.ArtifactName), however it is not required for this package type so it can still be released without API review approval." + } } # Ignore API review status for prerelease version Write-Host "Package version is not GA. Ignoring API view approval status" - } + } else { # Return error code if status code is 201 for new data plane package # Temporarily enable API review for spring SDK types. Ideally this should be done be using 'IsReviewRequired' method in language side # to override default check of SDK type client - if (IsApiviewStatusCheckRequired $pkgInfo) + if (IsApiviewStatusCheckRequired $packageInfo) { if (!$apiStatus.IsApproved) { - Write-Host "Package version $($version) is GA and automatic API Review is not yet approved for package $($packageName)." + Write-Host "Package version $($version) is GA and automatic API Review is not yet approved for package $($packageInfo.ArtifactName)." Write-Host "Build and release is not allowed for GA package without API review approval." Write-Host "You will need to queue another build to proceed further after API review is approved" Write-Host "You can check http://aka.ms/azsdk/engsys/apireview/faq for more details on API Approval." @@ -281,7 +294,7 @@ function ProcessPackage($packageName) return 1 } else { - Write-Host "API review is not approved for package $($packageName), however it is not required for this package type so it can still be released without API review approval." + Write-Host "API review is not approved for package $($packageInfo.ArtifactName), however it is not required for this package type so it can still be released without API review approval." } } } @@ -296,42 +309,84 @@ function ProcessPackage($packageName) return 0 } -$responses = @{} -# Check if package config file is present. This file has package version, SDK type etc info. -if (-not $ConfigFileDir) -{ +Write-Host "Artifact path: $($ArtifactPath)" +Write-Host "Source branch: $($SourceBranch)" +Write-Host "Package Info Files: $($PackageInfoFiles)" +Write-Host "Artifact List: $($ArtifactList)" +Write-Host "Package Name: $($PackageName)" + +# Parameter priority and backward compatibility logic +# Priority order: PackageName > Artifacts > PackageInfoFiles (for backward compatibility) + +if (-not $ConfigFileDir) { $ConfigFileDir = Join-Path -Path $ArtifactPath "PackageInfo" } -Write-Host "Artifact path: $($ArtifactPath)" -Write-Host "Source branch: $($SourceBranch)" Write-Host "Config File directory: $($ConfigFileDir)" -# if package name param is not empty then process only that package -if ($PackageName) -{ - Write-Host "Processing $($PackageName)" - $result = ProcessPackage -packageName $PackageName - $responses[$PackageName] = $result +# Initialize working variable +$ProcessedPackageInfoFiles = @() + +if ($PackageName -and $PackageName -ne "") { + # Highest Priority: Single package mode (existing usage) + Write-Host "Using PackageName parameter: $PackageName" + $pkgPropPath = Join-Path -Path $ConfigFileDir "$PackageName.json" + if (Test-Path $pkgPropPath) { + $ProcessedPackageInfoFiles = @($pkgPropPath) + } + else { + Write-Error "Package property file path $pkgPropPath is invalid." + exit 1 + } } -else -{ - # process all packages in the artifact - foreach ($artifact in $ArtifactList) - { - Write-Host "Processing $($artifact.name)" - $result = ProcessPackage -packageName $artifact.name - $responses[$artifact.name] = $result +elseif ($ArtifactList -and $ArtifactList.Count -gt 0) { + # Second Priority: Multiple artifacts mode (existing usage) + Write-Host "Using ArtifactList parameter with $($ArtifactList.Count) artifacts" + foreach ($artifact in $ArtifactList) { + $pkgPropPath = Join-Path -Path $ConfigFileDir "$($artifact.name).json" + if (Test-Path $pkgPropPath) { + $ProcessedPackageInfoFiles += $pkgPropPath + } + else { + Write-Warning "Package property file path $pkgPropPath is invalid." + } } } +elseif ($PackageInfoFiles -and $PackageInfoFiles.Count -gt 0) { + # Lowest Priority: Direct PackageInfoFiles (new method) + Write-Host "Using PackageInfoFiles parameter with $($PackageInfoFiles.Count) files" + $ProcessedPackageInfoFiles = $PackageInfoFiles # Use as-is +} +else { + Write-Error "No package information provided. Please provide either 'PackageName', 'ArtifactList', or 'PackageInfoFiles' parameters." + exit 1 +} + +# Validate that we have package info files to process +if (-not $ProcessedPackageInfoFiles -or $ProcessedPackageInfoFiles.Count -eq 0) { + Write-Error "No package info files found after processing parameters." + exit 1 +} + +$responses = @{} +Write-Host "Processed Package Info Files: $($ProcessedPackageInfoFiles -join ', ')" + +# Process all packages using the processed PackageInfoFiles array +foreach ($packageInfoFile in $ProcessedPackageInfoFiles) +{ + $packageInfo = Get-Content $packageInfoFile | ConvertFrom-Json + Write-Host "Processing $($packageInfo.ArtifactName)" + $result = ProcessPackage -packageInfo $packageInfo + $responses[$packageInfo.ArtifactName] = $result +} $exitCode = 0 foreach($pkg in $responses.keys) -{ +{ if ($responses[$pkg] -eq 1) { Write-Host "API changes are not approved for $($pkg)" $exitCode = 1 } } -exit $exitCode \ No newline at end of file +exit $exitCode From 274787d3c332d6acc2ee1e9c4f1141acdd47da91 Mon Sep 17 00:00:00 2001 From: ray chen Date: Mon, 13 Oct 2025 20:33:00 +0000 Subject: [PATCH 2/3] Use new signature for FindArtifactForApiReviewFn --- eng/common/scripts/Detect-Api-Changes.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/common/scripts/Detect-Api-Changes.ps1 b/eng/common/scripts/Detect-Api-Changes.ps1 index 1e9bfc4a0030..a2ed69b7a152 100644 --- a/eng/common/scripts/Detect-Api-Changes.ps1 +++ b/eng/common/scripts/Detect-Api-Changes.ps1 @@ -142,7 +142,7 @@ foreach ($packageInfoFile in $packageInfoFiles) # Call function with appropriate parameters if ($supportsPackageInfoParam) { LogInfo "Calling $FindArtifactForApiReviewFn with packageInfo parameter" - $packages = &$FindArtifactForApiReviewFn $ArtifactPath $pkgArtifactName $packageInfo + $packages = &$FindArtifactForApiReviewFn $ArtifactPath $packageInfo } else { LogInfo "Calling $FindArtifactForApiReviewFn with legacy parameters" From 85ea89d08747c7d5fc53a8445c88a4126cca40a5 Mon Sep 17 00:00:00 2001 From: ray chen Date: Tue, 14 Oct 2025 16:01:56 +0000 Subject: [PATCH 3/3] Used new signature of FindArtifactForApiReviewFn --- eng/common/scripts/Create-APIReview.ps1 | 2 +- eng/common/scripts/Detect-Api-Changes.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index a4b4d401e9e1..92e614f4a2d5 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -193,7 +193,7 @@ function ProcessPackage($packageInfo) # Call function with appropriate parameters if ($supportsPackageInfoParam) { LogInfo "Calling $FindArtifactForApiReviewFn with packageInfo parameter" - $packages = &$FindArtifactForApiReviewFn $ArtifactPath $pkgArtifactName $packageInfo + $packages = &$FindArtifactForApiReviewFn $ArtifactPath $packageInfo } else { LogInfo "Calling $FindArtifactForApiReviewFn with legacy parameters" diff --git a/eng/common/scripts/Detect-Api-Changes.ps1 b/eng/common/scripts/Detect-Api-Changes.ps1 index a2ed69b7a152..8c3807e31820 100644 --- a/eng/common/scripts/Detect-Api-Changes.ps1 +++ b/eng/common/scripts/Detect-Api-Changes.ps1 @@ -132,13 +132,13 @@ foreach ($packageInfoFile in $packageInfoFiles) # Check if the function supports the packageInfo parameter $functionInfo = Get-Command $FindArtifactForApiReviewFn -ErrorAction SilentlyContinue $supportsPackageInfoParam = $false - + if ($functionInfo -and $functionInfo.Parameters) { # Check if function specifically supports packageInfo parameter $parameterNames = $functionInfo.Parameters.Keys $supportsPackageInfoParam = $parameterNames -contains 'packageInfo' } - + # Call function with appropriate parameters if ($supportsPackageInfoParam) { LogInfo "Calling $FindArtifactForApiReviewFn with packageInfo parameter"