testcases/deploymentTemplate/apiVersions-Should-Be-Recent.test.ps1
<# .Synopsis Ensures the apiVersions are recent. .Description Ensures the apiVersions of any resources are recent and non-preview. .Example Test-AzTemplate -TemplatePath .\100-marketplace-sample\ -Test apiVersions-Should-Be-Recent .Example .\apiVersions-Should-Be-Recent.test.ps1 -TemplateObject ( Get-Content ..\..\..\..\100-marketplace-sample\azureDeploy.json | ConvertFrom-Json ) -AllAzureResources ( Get-Content ..\..\cache\AllAzureResources.cache.json | ConvertFrom-Json ) -TestDate ( [datetime]::ParseExact("31/08/2019", "dd-mm-yy", $null) ) #> param( # The resource in the main template [Parameter(Mandatory = $true, Position = 0)] [PSObject] $TemplateObject, # All potential resources in Azure (from cache) [Parameter(Mandatory = $true, Position = 2)] [PSObject] $AllAzureResources, # Number of days that the apiVersion must be less than [Parameter(Mandatory = $false, Position = 3)] [int32] $NumberOfDays = 730, # Test Run Date - date to use when doing comparisons, if not current date (used for unit testing against and old cache) [Parameter(Mandatory = $false, Position = 3)] [datetime] $TestDate = [DateTime]::Now ) if (-not $TemplateObject.resources) { # If we don't have any resources # then it's probably a partial template, and there's no apiVersions to check anyway, return # so return. } # First, find all of the API versions in the main template resources. $allApiVersions = $TemplateObject.resources | Find-JsonContent -Key apiVersion -Value * -Like foreach ($av in $allApiVersions) { # Then walk over each object containing an ApiVersion. if ($av.ApiVersion -isnot [string]) { # If the APIVersion is not a string # write an error Write-Error "Api Versions must be strings" -TargetObject $av -ErrorId ApiVersion.Not.String continue # and continue. } # Next, resolve the full resource type $FullResourceTypes = @( if ($av.ParentObject) { # by walking backwards over the parent resources # (since the topmost resource will be the last item in the list) for ($i = $av.ParentObject.Count - 1; $i -ge 0; $i--) { if (-not $av.ParentObject[$i].type) { continue } $av.ParentObject[$i].type } } $av.type # and adding this resource's type. ) # To get the full type name, join them all with a slash $FullResourceType = $FullResourceTypes -join '/' # Now, get the API version as a string $apiString = $av.ApiVersion $hasDate = $apiString -match "(?<Year>\d{4,4})-(?<Month>\d{2,2})-(?<Day>\d{2,2})" if (-not $hasDate) { # If we couldn't, write an error Write-Error "Api versions must be a fixed date. $FullResourceType is not." -TargetObject $av -ErrorId ApiVersion.Not.Date continue # and move onto the next resource } $apiDate = [DateTime]::new($matches.Year, $matches.Month, $matches.Day) # now coerce the apiVersion into a DateTime # Now find all of the valid versions from this API $validApiVersions = # This is made a little tricky by the fact that some resources don't directly have an API version @(for ($i = $FullResourceTypes.Count - 1; $i -ge 0; $i--) { # so we need to walk backwards thru the list of items $resourceTypeName = $FullResourceTypes[0..$i] -join '/' # construct the resource type name $apiVersionsOfType = $AllAzureResources.$resourceTypeName | # and see if there's an apiVersion. Select-Object -ExpandProperty apiVersions | Sort-Object -Descending if ($apiVersionsOfType) { # If there was, $apiVersionsOfType # set it and break the loop break } }) # Create a string of recent or allowed apiVersions for display in the error message $recentApiVersions = "" foreach ($v in $validApiVersions) { $hasDate = $v -match "(?<Year>\d{4,4})-(?<Month>\d{2,2})-(?<Day>\d{2,2})" $vDate = [DateTime]::new($matches.Year, $matches.Month, $matches.Day) # if the apiVersion is "recent" or the latest one add it to the list (note $validApiVersions is sorted) # note "recent" means is it new enough that it's allowed by the test if ($($TestDate - $vDate).TotalDays -lt $NumberOfDays -or $v -eq $validApiVersions[0]) { # TODO: when the only recent versions are a preview version and a non-preview of the same date, $recentApiVersions will only contain the preview # due to sorting, which is incorrect $recentApiVersions += " $v`n" } } $howOutOfDate = $validApiVersions.IndexOf($av.ApiVersion) # Find out how out of date we are. # Is the apiVersion even in the list? if ($howOutOfDate -eq -1 -and $validApiVersions) { # Removing the error for this now - this is happening with the latest versions and outdated manifests # We can assume that if the version is indeed invalid, deployment will fail # Write-Error "$fullResourceType is using an invalid apiVersion." -ErrorId ApiVersion.Not.Valid # Write-Output "ApiVersion not found for: $fullResourceType and version $($av.apiVersion)" # Write-Output "Valid Api Versions found:`n$recentApiVersions" } if ($av.ApiVersion -like '*-*-*-*') { # If it's a preview or other special variant, e.g. 2016-01-01-preview $moreRecent = $validApiVersions[0..$howOutOfDate] # see if there's a more recent non-preview version. if ($howOutOfDate -gt 0) { Write-Error "$FullResourceType uses a preview version ( $($av.apiVersion) ) and there are more recent versions available." -TargetObject $av -ErrorId ApiVersion.Preview.Not.Recent Write-Output "Valid Api Versions:`n$recentApiVersions" } } # Finally, check how long it's been since the ApiVersion's date $timeSinceApi = $TestDate - $apiDate if (($timeSinceApi.TotalDays -gt $NumberOfDays) -and ($howOutOfDate -gt 0)) { # if the used apiVersion is the second in the list, check to see if the first in the list is the same preview version (due to sorting) # for example: "2017-12-01-preview" and "2017-12-01" - the preview is sorted first so we think we're out of date $nonPreviewVersionInUse = $false if ($howOutOfDate -eq 1) { $trimmedApiVersion = $validApiVersions[0].ToString().Substring(0, $validApiVersions[0].ToString().LastIndexOf("-")) $nonPreviewVersionInUse = ($trimmedApiVersion -eq $av.apiVersion) } if (-not $nonPreviewVersionInUse) { # If it's older than two years, and there's nothing more recent Write-Error "Api versions must be the latest or under $($NumberOfDays / 365) years old ($NumberOfDays days) - API version $($av.ApiVersion) of $FullResourceType is $([Math]::Floor($timeSinceApi.TotalDays)) days old" -ErrorId ApiVersion.OutOfDate Write-Output "Valid Api Versions:`n$recentApiVersions" } } } |