Skip to content

Commit

Permalink
New API version script (#27932)
Browse files Browse the repository at this point in the history
* Parameters validation and completer suggestions

* Update readme with script

* Commit readme changes in script

* Update script to avoid adding a new line at the eof

* Apply suggestions

* Make readme commit message more meaningful

* Simplify script and make the base version required and fix regex

* Refactor script to start writing tests for it

* Add test for tag generation

* Support subdirectories inside the provider dir

* Support provider subdirectories and use package-YYYY-MM convention for tags

* Update tests and test assets

* Fix problem in function

* Remove unnecessary test files

* Use single quotes to avoid scaping the backtick

* Improve regex and readability

* Refactor tests to avoid depending on input files

* Format documents and don't use regex to create DateTime objects

* Uncomment lines of code
  • Loading branch information
JonathanCrd authored Apr 4, 2024
1 parent 08f7f27 commit 15848b6
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 47 deletions.
66 changes: 66 additions & 0 deletions eng/scripts/Copy-ApiVersion-Functions.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
function Get-NewTagSection($apiVersion, $resourceProvider, $apiVersionStatus, $specFiles) {

$tagVersion = $apiVersion -match '(?<date>\d{4}-\d{2})-\d{2}'
$tagVersion = $Matches['date']
$baseDir = "$resourceProvider/$apiVersionStatus/$apiVersion"

if ($apiVersionStatus -eq "preview") {
$tagVersion = "preview-" + $tagVersion
}

$content = @"
### Tag: package-$tagVersion
These settings apply only when ``--tag=package-$tagVersion`` is specified on the command line.
```````yaml `$(tag) == 'package-$tagVersion'`
input-file:
"@

foreach ($specFile in $specFiles) {
$content += "`n - $baseDir/$specFile"
}

$content += "`n" + '```' + "`n"
return $content
}

function Get-ReadmeWithNewTag($readmeContent, $tagContent) {
return $readmeContent -replace '(?s)(### tag: package.*)', "$tagContent`n`$1"
}

function Get-ReadmeWithLatestTag($readmeContent, $newApiVersion, $newApiVersionStatus ) {
# Get the current tag date
$currentTag = $readmeContent -match '(?m)^(tag:\s*)(package-)(.*)(?<version>\d{4}-\d{2})(.*)'
$currentTag = $Matches['version']
$latestVersionDate = [datetime]($currentTag -replace '-preview', '')

# Convert the new OpenAPI version to a date
$newVersionDate = [datetime]($newApiVersion -replace '-preview', '')

# Compare two dates
if ($latestVersionDate -gt $newVersionDate) {
Write-Warning "The new version is not newer than the current default version in the readme file."
}
$tagVersion = $newApiVersion -match '\d{4}-\d{2}'
$tagVersion = $Matches[0]
if ($newApiVersionStatus -eq "preview") {
$tagVersion = "preview-" + $tagVersion
}
return $readmeContent -replace '(?m)^(tag:\s*)(package-.*)', "tag: package-$tagVersion"
}

function New-GitAddAndCommit {
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory = $true)]
[string]$directory,
[Parameter(Mandatory = $true)]
[string]$message
)

if ($PSCmdlet.ShouldProcess($directory, "Add and commit")) {
git add $directory | Out-Null
$message | git commit --file=-
}
}
158 changes: 111 additions & 47 deletions eng/scripts/Copy-ApiVersion.ps1
Original file line number Diff line number Diff line change
@@ -1,77 +1,141 @@
[CmdletBinding()]
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory = $true)]
[ValidateScript({
if (-not (Test-Path "$PSScriptRoot/../../specification/$_")) {
throw "Service directory not found in the specification folder."
}
$true
})]
[ArgumentCompleter({
Get-ChildItem "$PSScriptRoot/../../specification/" -Directory | Select-Object -ExpandProperty Name
})]
[string] $ServiceDirectory,

[Parameter()]
[Parameter(Mandatory = $true)]
[ValidateSet('data-plane', 'resource-manager')]
[string] $ServiceType = 'data-plane',
[ArgumentCompleter({ 'data-plane', 'resource-manager' })]
[string] $ServiceType,

[Parameter(Mandatory = $true)]
[string] $Provider,

[Parameter()]
[ValidateScript({
function script:Get-Version([string] $version) {
if ($version -match '^(?<status>stable|preview)/(?<version>(\d{4}-\d{2}-\d{2}|\d+\.\d+)(-preview(\.\d+)?)?)$') {
return $Matches['status'], $Matches['version']
if (-not (Test-Path "$PSScriptRoot/../../specification/$ServiceDirectory/$ServiceType/$_")) {
$validProviders = (Get-ChildItem "$PSScriptRoot/../../specification/$ServiceDirectory/$ServiceType/" -Directory).Name -join ', '
throw "Service Provider not found. Valid options are: $validProviders"
}
$true
})]
[ArgumentCompleter({
param($commandName,
$parameterName,
$wordToComplete,
$commandAst,
$fakeBoundParameters)
Get-ChildItem "$PSScriptRoot/../../specification/$($fakeBoundParameters.ServiceDirectory)/$($fakeBoundParameters.ServiceType)/" -Directory | Select-Object -ExpandProperty Name
})]
[string] $Provider,

[Parameter(Mandatory = $true)]
[ArgumentCompleter({ "preview/", "stable/" })]
[ValidateScript({
function script:Get-Version([string] $version) {
if ($version -match '^(?<status>stable|preview)/(?<version>(\d{4}-\d{2}-\d{2}|\d+\.\d+)(-preview(\.\d+)?)?)$') {
return $Matches['status'], $Matches['version']
}

throw 'Version must start with "stable/" or "preview/" and end with either a date-based version (recommended) like 2024-01-05 or a major.minor semver, followed by an optional "-preview" for previews APIs.'
}
# Throwing or returning truthy is sufficient.
Get-Version $_
})]
[string] $FromVersion,

[Parameter()]
[ValidateScript({Get-Version $_})]
[string] $ToVersion
throw 'Version must start with "stable/" or "preview/" and end with either a date-based version (recommended) like 2024-01-05 or a major.minor semver, followed by an optional "-preview" for previews APIs.'
}
Get-Version $_ })]
[string] $NewVersion,

[Parameter(Mandatory = $true)]
[ValidateScript({ Get-Version $_ })]
[ArgumentCompleter({
param($commandName,
$parameterName,
$wordToComplete,
$commandAst,
$fakeBoundParameters)
[string[]] $stableVersions = Get-ChildItem "$PSScriptRoot/../../specification/$($fakeBoundParameters.ServiceDirectory)/$($fakeBoundParameters.ServiceType)/$($fakeBoundParameters.Provider)/stable/" -Directory | Select-Object -ExpandProperty Name |
ForEach-Object { "stable/$_" }
[string[]] $previewVersions = Get-ChildItem "$PSScriptRoot/../../specification/$($fakeBoundParameters.ServiceDirectory)/$($fakeBoundParameters.ServiceType)/$($fakeBoundParameters.Provider)/preview/" -Directory | Select-Object -ExpandProperty Name |
ForEach-Object { "preview/$_" }
return $stableVersions + $previewVersions
})]
[string] $BaseVersion
)

$ErrorActionPreference = 'Stop'
. "$PSScriptRoot/Copy-ApiVersion-Functions.ps1"

$oldVersionStatus, $oldVersion = Get-Version $FromVersion
$newVersionStatus, $newVersion = Get-Version $ToVersion
$oldApiVersionStatus, $oldApiVersion = Get-Version $BaseVersion
$newApiVersionStatus, $newApiVersion = Get-Version $NewVersion

$repoDirectory = Resolve-Path "$PSScriptRoot/../.."
$readmeDirectory = Join-Path $repoDirectory "specification/$ServiceDirectory/$ServiceType" -Resolve
$oldDirectory = Join-Path $repoDirectory "specification/$ServiceDirectory/$ServiceType/$Provider/$oldVersionStatus/$oldVersion" -Resolve
$newDirectory = Join-Path $repoDirectory "specification/$ServiceDirectory/$ServiceType/$Provider/$newVersionStatus/$newVersion"

$oldDirectory = Join-Path $repoDirectory "specification/$ServiceDirectory/$ServiceType/$Provider/$oldApiVersionStatus/$oldApiVersion" -Resolve
$newDirectory = Join-Path $repoDirectory "specification/$ServiceDirectory/$ServiceType/$Provider/$newApiVersionStatus/$newApiVersion"

Write-Host "----------------------------------------"
Write-Host "Service Directory: $ServiceDirectory"
Write-Host "Service Type: $ServiceType"
Write-Host "Provider: $Provider"
Write-Host "Base Version: $BaseVersion"
Write-Host "New Version: $NewVersion"
Write-Host "----------------------------------------"

Write-Verbose "Copying $oldDirectory to $newDirectory"

# Copy the specs and create and initial commit to make it easier to diff changes to the previous version in a PR.
Copy-Item $oldDirectory $newDirectory -Recurse -Force

git add $newDirectory
@"
Copy files from $FromVersion
New-GitAddAndCommit $newDirectory @"
Copy files from $BaseVersion
Copied the files in a separate commit.
This allows reviewers to easily diff subsequent changes against the previous spec.
"@ | git commit --file=-
"@

# Replace the $oldVersion with the $newVersion within all files in $newDirectory.
# Replace the $oldApiVersion with the $newApiVersion within all files in $newDirectory.
foreach ($file in Get-ChildItem $newDirectory -File -Recurse) {
Write-Verbose "Replacing any API versions in $file"
$content = $file | Get-Content -Raw
$content -replace $oldVersion, $newVersion | Set-Content $file.FullName
$content -replace $oldApiVersion, $newApiVersion | Set-Content $file.FullName
}

# Commit just the version changes.
git add $newDirectory
@"
Update version to $ToVersion
Updated the API version from $FromVersion to $ToVersion.
"@ | git commit --file=-

Write-Host ''
Write-Host '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' -ForegroundColor 'Yellow'
Write-Host '!!! IMPORTANT: Action Required !!!' -ForegroundColor 'Yellow'
Write-Host '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' -ForegroundColor 'Yellow'
Write-Host ''
Write-Host "You must manually update the default version and copy $FromVersion sections from any markdown files in $readmeDirectory."
Write-Host 'When complete, commit those changes as shown below, push to the upstream repo, and submit a pull request.'
Write-Host ''
Write-Host ' git commit -am"Updated readme files"'
Write-Host ''
New-GitAddAndCommit $newDirectory @"
Update version to $NewVersion
Updated the API version from $BaseVersion to $NewVersion.
"@

# Add new version tag in the readme file
$readmeFile = Get-ChildItem $readmeDirectory -Filter 'readme.md'
if ($readmeFile) {
$jsonFiles = Get-ChildItem $newDirectory -Filter '*.json' | Select-Object -ExpandProperty Name
$newReadmeTagBlock = Get-NewTagSection $newApiVersion $Provider $newApiVersionStatus $jsonFiles

$readmeContent = $readmeFile | Get-Content -Raw
$readmeContent = Get-ReadmeWithNewTag $readmeContent $newReadmeTagBlock
$readmeContent | Set-Content $readmeFile.FullName -NoNewline
}
else {
Write-Error "No readme file found in $readmeDirectory"
Exit 1
}

# Update the latest version tag in the readme file
$val = Get-ReadmeWithLatestTag $readmeContent $newApiVersion $newApiVersionStatus
if ($val -ne "") {
Write-Verbose "Updating the first tag in the first yaml code block in $readmeFile"
$val | Set-Content $readmeFile.FullName -NoNewline
Write-Information "Latest version tag in readme file updated."
}
else {
Write-Warning "The new version is not newer than the current version in the readme file. No changes were made."
}

New-GitAddAndCommit $readmeDirectory @"
Added tag for $newApiVersion in readme file
"@
58 changes: 58 additions & 0 deletions eng/scripts/Tests/Copy-ApiVersion/Copy-ApiVersion.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
Import-Module Pester

. "$PSScriptRoot\..\..\Copy-ApiVersion-Functions.ps1"

Describe "Copy-ApiVersion regex tests" {
Context "Generate new version tag section" {
It "Section tag corresponds to new version" -TestCases @(
@{
version = "2024-01-01-preview"
provider = "Microsoft.AgFoodPlatform"
versionStatus = "preview"
specsDir = '..\..\..\..\..\specification\agrifood\resource-manager\Microsoft.AgFoodPlatform\preview\2023-06-01-preview'
},
@{
version = "2024-01-01"
provider = "Microsoft.Compute\ComputeRP"
versionStatus = "stable"
specsDir = '..\..\..\..\..\specification\compute\resource-manager\Microsoft.Compute\ComputeRP\stable\2023-09-01'
}
) {
param($version, $provider, $versionStatus, $specsDir)

$jsonFiles = Get-ChildItem $PSScriptRoot+$specsDir -Filter '*.json' | Select-Object -ExpandProperty Name
$newTagSection = Get-NewTagSection $version $provider $versionStatus $jsonFiles

$version -match '\d{4}-\d{2}'
$tag = $Matches[0]
if ($versionStatus -eq "preview") {
$tag = "preview-" + $tag
}

$newTagSection | Should match "package-$tag"
}

It "Default version gets updated" -TestCases @(
@{
inputReadme = '..\..\..\..\..\specification\agrifood\resource-manager\readme.md'
apiVersion = "2024-01-01-preview"
versionStatus = "preview"
},
@{
inputReadme = '..\..\..\..\..\specification\compute\resource-manager\readme.md'
apiVersion = "2024-01-01"
versionStatus = "stable"
}
) {
param($inputReadme, $apiVersion, $versionStatus)
$contents = Get-Content $PSScriptRoot+$inputReadme -Raw
$output = Get-ReadmeWithLatestTag $contents $apiVersion $versionStatus
$tag = $apiVersion -match '\d{4}-\d{2}'
$tag = $Matches[0]
if ($versionStatus -eq "preview") {
$tag = "preview-" + $tag
}
$output | Should match "(?m)^tag:\s*package-$tag"
}
}
}

0 comments on commit 15848b6

Please sign in to comment.