microsoft/typespec

Public

mirrored fromhttps://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
efeedc42a9fb908c64751d97fea2ae3d7dfeb4ee

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

eng/emitters/scripts/Create-APIReview.ps1

380lines · modecode

1[CmdletBinding()]
2Param (
3 [Parameter(Mandatory=$True)]
4 [array] $ArtifactList,
5 [Parameter(Mandatory=$True)]
6 [string] $ArtifactPath,
7 [string] $SourceBranch,
8 [string] $DefaultBranch,
9 [string] $RepoName,
10 [string] $BuildId,
11 [string] $PackageName = "",
12 [string] $ConfigFileDir = "",
13 [string] $APIViewUri = "https://apiview.dev/autoreview",
14 [string] $ArtifactName = "packages",
15 [bool] $MarkPackageAsShipped = $false,
16 [string] $LanguageShortName = "Unknown"
17)
18
19Set-StrictMode -Version 3
20. (Join-Path $PSScriptRoot ApiView-Helpers.ps1)
21. (Join-Path $PSScriptRoot SemVer.ps1)
22
23# Get Bearer token for APIView authentication
24# In Azure DevOps, this uses the service connection's Managed Identity/Service Principal
25function Get-ApiViewBearerToken()
26{
27 try {
28 $tokenResponse = az account get-access-token --resource "api://apiview" --output json 2>&1
29 if ($LASTEXITCODE -ne 0) {
30 Write-Error "Failed to acquire access token. Please ensure Azure CLI is authenticated and has access to the APIView resource."
31 return $null
32 }
33 return ($tokenResponse | ConvertFrom-Json).accessToken
34 }
35 catch {
36 Write-Error "Failed to acquire access token: $($_.Exception.Message)"
37 return $null
38 }
39}
40
41if ($LanguageShortName -eq "Unknown")
42{
43 Write-Host "Language short name is not provided. Please provide the language short name."
44 exit 1
45}
46else
47{
48 $functionScriptPath = Join-Path $PSScriptRoot "/../../../packages/http-client-$LanguageShortName/eng/scripts/Functions.ps1"
49 if (!(Test-Path $functionScriptPath))
50 {
51 Write-Host "Functions script path $($functionScriptPath) is invalid."
52 exit 1
53 }
54 . ($functionScriptPath)
55}
56
57# Submit API review request and return status whether current revision is approved or pending or failed to create review
58function Upload-SourceArtifact($filePath, $apiLabel, $releaseStatus, $packageVersion)
59{
60 Write-Host "File path: $filePath"
61 $fileName = Split-Path -Leaf $filePath
62 Write-Host "File name: $fileName"
63 $multipartContent = [System.Net.Http.MultipartFormDataContent]::new()
64 $FileStream = [System.IO.FileStream]::new($filePath, [System.IO.FileMode]::Open)
65 $fileHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
66 $fileHeader.Name = "file"
67 $fileHeader.FileName = $fileName
68 $fileContent = [System.Net.Http.StreamContent]::new($FileStream)
69 $fileContent.Headers.ContentDisposition = $fileHeader
70 $fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("application/octet-stream")
71 $multipartContent.Add($fileContent)
72
73
74 $stringHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
75 $stringHeader.Name = "label"
76 $StringContent = [System.Net.Http.StringContent]::new($apiLabel)
77 $StringContent.Headers.ContentDisposition = $stringHeader
78 $multipartContent.Add($stringContent)
79 Write-Host "Request param, label: $apiLabel"
80
81 $versionParam = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
82 $versionParam.Name = "packageVersion"
83 $versionContent = [System.Net.Http.StringContent]::new($packageVersion)
84 $versionContent.Headers.ContentDisposition = $versionParam
85 $multipartContent.Add($versionContent)
86 Write-Host "Request param, packageVersion: $packageVersion"
87
88 $releaseTagParam = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
89 $releaseTagParam.Name = "setReleaseTag"
90 $releaseTagParamContent = [System.Net.Http.StringContent]::new($MarkPackageAsShipped)
91 $releaseTagParamContent.Headers.ContentDisposition = $releaseTagParam
92 $multipartContent.Add($releaseTagParamContent)
93 Write-Host "Request param, setReleaseTag: $MarkPackageAsShipped"
94
95 if ($releaseStatus -and ($releaseStatus -ne "Unreleased"))
96 {
97 $compareAllParam = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
98 $compareAllParam.Name = "compareAllRevisions"
99 $compareAllParamContent = [System.Net.Http.StringContent]::new($true)
100 $compareAllParamContent.Headers.ContentDisposition = $compareAllParam
101 $multipartContent.Add($compareAllParamContent)
102 Write-Host "Request param, compareAllRevisions: true"
103 }
104
105 $uri = "${APIViewUri}/upload"
106
107 # Get Bearer token for authentication
108 $bearerToken = Get-ApiViewBearerToken
109 if (-not $bearerToken) {
110 Write-Error "Failed to acquire Bearer token for APIView authentication."
111 return [System.Net.HttpStatusCode]::Unauthorized
112 }
113
114 $headers = @{
115 "Authorization" = "Bearer $bearerToken";
116 "content-type" = "multipart/form-data"
117 }
118
119 try
120 {
121 $Response = Invoke-WebRequest -Method 'POST' -Uri $uri -Body $multipartContent -Headers $headers
122 Write-Host "API review: $($Response.Content)"
123 $StatusCode = $Response.StatusCode
124 }
125 catch
126 {
127 Write-Host "ERROR: API request failed" -ForegroundColor Red
128 Write-Host "Status Code: $($_.Exception.Response.StatusCode.Value__)" -ForegroundColor Yellow
129 Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Yellow
130 if ($_.ErrorDetails.Message) {
131 Write-Host "Details: $($_.ErrorDetails.Message)" -ForegroundColor Yellow
132 }
133 $StatusCode = $_.Exception.Response.StatusCode
134 }
135
136 return $StatusCode
137}
138
139function Upload-ReviewTokenFile($packageName, $apiLabel, $releaseStatus, $reviewFileName, $packageVersion, $filePath)
140{
141 Write-Host "Original File path: $filePath"
142 $fileName = Split-Path -Leaf $filePath
143 Write-Host "OriginalFile name: $fileName"
144
145 $params = "buildId=${BuildId}&artifactName=${ArtifactName}&originalFilePath=${fileName}&reviewFilePath=${reviewFileName}"
146 $params += "&label=${apiLabel}&repoName=${RepoName}&packageName=${packageName}&project=internal&packageVersion=${packageVersion}"
147 if($MarkPackageAsShipped) {
148 $params += "&setReleaseTag=true"
149 }
150 $uri = "${APIViewUri}/create?${params}"
151 if ($releaseStatus -and ($releaseStatus -ne "Unreleased"))
152 {
153 $uri += "&compareAllRevisions=true"
154 }
155
156 Write-Host "Request to APIView: $uri"
157
158 # Get Bearer token for authentication
159 $bearerToken = Get-ApiViewBearerToken
160 if (-not $bearerToken) {
161 Write-Error "Failed to acquire Bearer token for APIView authentication."
162 return [System.Net.HttpStatusCode]::Unauthorized
163 }
164
165 $headers = @{
166 "Authorization" = "Bearer $bearerToken"
167 }
168
169 try
170 {
171 $Response = Invoke-WebRequest -Method 'POST' -Uri $uri -Headers $headers
172 Write-Host "API review: $($Response.Content)"
173 $StatusCode = $Response.StatusCode
174 }
175 catch
176 {
177 Write-Host "ERROR: API request failed" -ForegroundColor Red
178 Write-Host "Status Code: $($_.Exception.Response.StatusCode.Value__)" -ForegroundColor Yellow
179 Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Yellow
180 if ($_.ErrorDetails.Message) {
181 Write-Host "Details: $($_.ErrorDetails.Message)" -ForegroundColor Yellow
182 }
183 $StatusCode = $_.Exception.Response.StatusCode
184 }
185
186 return $StatusCode
187}
188
189function Get-APITokenFileName($packageName)
190{
191 $reviewTokenFileName = ${packageName}.StartsWith("typespec-") ? "${packageName}_js.json" : "${packageName}_${LanguageShortName}.json"
192 $tokenFilePath = Join-Path $ArtifactPath $packageName $reviewTokenFileName
193 if (Test-Path $tokenFilePath) {
194 Write-Host "Review token file is present at $tokenFilePath"
195 return $reviewTokenFileName
196 }
197 else {
198 Write-Host "Review token file is not present at $tokenFilePath"
199 return $null
200 }
201}
202
203function Submit-APIReview($packageInfo, $packagePath, $packageArtifactName)
204{
205 $packageName = $packageInfo.Name
206 $apiLabel = "Source Branch:${SourceBranch}"
207
208 # Get generated review token file if present
209 # APIView processes request using different API if token file is already generated
210 $reviewTokenFileName = Get-APITokenFileName $packageArtifactName
211 if ($reviewTokenFileName) {
212 Write-Host "Uploading review token file $reviewTokenFileName to APIView."
213 return Upload-ReviewTokenFile $packageArtifactName $apiLabel $packageInfo.ReleaseStatus $reviewTokenFileName $packageInfo.Version $packagePath
214 }
215 else {
216 Write-Host "Uploading $packagePath to APIView."
217 return Upload-SourceArtifact $packagePath $apiLabel $packageInfo.ReleaseStatus $packageInfo.Version
218 }
219}
220
221function IsApiviewStatusCheckRequired($packageInfo)
222{
223 if (($packageInfo.SdkType -eq "client" -or $packageInfo.SdkType -eq "spring") -and $packageInfo.IsNewSdk) {
224 return $true
225 }
226 return $false
227}
228
229function ProcessPackage($packageName)
230{
231 $packages = Find-Artifacts-For-Apireview $ArtifactPath $packageName
232 if ($packages)
233 {
234 foreach($pkgPath in $packages.Values)
235 {
236 $pkg = Split-Path -Leaf $pkgPath
237 $pkgPropPath = Join-Path -Path $ConfigFileDir "$packageName.json"
238 if (-Not (Test-Path $pkgPropPath))
239 {
240 Write-Host " Package property file path $($pkgPropPath) is invalid."
241 continue
242 }
243 # Get package info from json file created before updating version to daily dev
244 $pkgInfo = Get-Content $pkgPropPath | ConvertFrom-Json
245 Write-Host "Processing package: $($pkgInfo)"
246 $version = [AzureEngSemanticVersion]::ParseVersionString($pkgInfo.Version)
247 if ($null -eq $version)
248 {
249 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."
250 return 1
251 }
252
253 Write-Host "Version: $($version)"
254 Write-Host "SDK Type: $($pkgInfo.SdkType)"
255 Write-Host "Release Status: $($pkgInfo.ReleaseStatus)"
256
257 # Run create review step only if build is triggered from main branch or if version is GA.
258 # This is to avoid invalidating review status by a build triggered from feature branch
259 if ( ($SourceBranch -eq $DefaultBranch) -or (-not $version.IsPrerelease) -or $MarkPackageAsShipped)
260 {
261 Write-Host "Submitting API Review request for package $($pkg), File path: $($pkgPath)"
262 $respCode = Submit-APIReview $pkgInfo $pkgPath $packageName
263 Write-Host "HTTP Response code: $($respCode)"
264
265 # no need to check API review status when marking a package as shipped
266 if ($MarkPackageAsShipped)
267 {
268 if ($respCode -eq '500')
269 {
270 Write-Host "Failed to mark package ${packageName} as released. Please reach out to Azure SDK engineering systems on teams channel."
271 return 1
272 }
273 Write-Host "Package ${packageName} is marked as released."
274 return 0
275 }
276
277 $apiStatus = [PSCustomObject]@{
278 IsApproved = $false
279 Details = ""
280 }
281 $pkgNameStatus = [PSCustomObject]@{
282 IsApproved = $false
283 Details = ""
284 }
285 Process-ReviewStatusCode $respCode $packageName $apiStatus $pkgNameStatus
286
287 if ($apiStatus.IsApproved) {
288 Write-Host "API status: $($apiStatus.Details)"
289 }
290 elseif (!$pkgInfo.ReleaseStatus -or $pkgInfo.ReleaseStatus -eq "Unreleased") {
291 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."
292 }
293 elseif ($version.IsPrerelease)
294 {
295 # Check if package name is approved. Preview version cannot be released without package name approval
296 if (!$pkgNameStatus.IsApproved)
297 {
298 if (IsApiviewStatusCheckRequired $pkgInfo)
299 {
300 Write-Error $($pkgNameStatus.Details)
301 return 1
302 }
303 else{
304 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."
305 }
306 }
307 # Ignore API review status for prerelease version
308 Write-Host "Package version is not GA. Ignoring API view approval status"
309 }
310 else
311 {
312 # Return error code if status code is 201 for new data plane package
313 # Temporarily enable API review for spring SDK types. Ideally this should be done be using 'IsReviewRequired' method in language side
314 # to override default check of SDK type client
315 if (IsApiviewStatusCheckRequired $pkgInfo)
316 {
317 if (!$apiStatus.IsApproved)
318 {
319 Write-Host "Package version $($version) is GA and automatic API Review is not yet approved for package $($packageName)."
320 Write-Host "Build and release is not allowed for GA package without API review approval."
321 Write-Host "You will need to queue another build to proceed further after API review is approved"
322 Write-Host "You can check http://aka.ms/azsdk/engsys/apireview/faq for more details on API Approval."
323 }
324 return 1
325 }
326 else {
327 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."
328 }
329 }
330 }
331 else {
332 Write-Host "Build is triggered from $($SourceBranch) with prerelease version. Skipping API review status check."
333 }
334 }
335 }
336 else {
337 Write-Host "No package is found in artifact path to submit review request"
338 }
339 return 0
340}
341
342$responses = @{}
343# Check if package config file is present. This file has package version, SDK type etc info.
344if (-not $ConfigFileDir)
345{
346 $ConfigFileDir = Join-Path -Path $ArtifactPath "PackageInfo"
347}
348
349Write-Host "Artifact path: $($ArtifactPath)"
350Write-Host "Source branch: $($SourceBranch)"
351Write-Host "Config File directory: $($ConfigFileDir)"
352
353# if package name param is not empty then process only that package
354if ($PackageName)
355{
356 Write-Host "Processing $($PackageName)"
357 $result = ProcessPackage -packageName $PackageName
358 $responses[$PackageName] = $result
359}
360else
361{
362 # process all packages in the artifact
363 foreach ($artifact in $ArtifactList)
364 {
365 Write-Host "Processing $($artifact.name)"
366 $result = ProcessPackage -packageName $artifact.name
367 $responses[$artifact.name] = $result
368 }
369}
370
371$exitCode = 0
372foreach($pkg in $responses.keys)
373{
374 if ($responses[$pkg] -eq 1)
375 {
376 Write-Host "API changes are not approved for $($pkg)"
377 $exitCode = 1
378 }
379}
380exit $exitCode
381