ps/Modules/Alkami.PowerShell.Choco/Public/Get-PackageInstallationData.ps1

250 lines
11 KiB
PowerShell
Raw Normal View History

2023-05-30 22:51:22 -07:00
function Get-PackageInstallationData {
<#
.SYNOPSIS
Returns an array of tiered packages to install, and attaches metadata to each package object about how the package should be installed. This does not modify the original $packages list.
.PARAMETER ChocoPackages
Array of [object] Packages with Name, Version, and Feed info
.PARAMETER Servers
Array of [string] Server hostnames used to decide which hosttypes should be signalled as targets for a given ChocoPackage
.PARAMETER FIlterFeeds
Causes the filtering out of ChocoPackages whose Feed property is undesirable for a deploy; for example, "Default", nuget.internal, SRETools.
.PARAMETER FilterPowerShellModules
Causes the filtering out of ChocoPackages that are PowerShellModules
.PARAMETER NugetCredential
Credential information to access the nuget server
.PARAMETER IncludeMissingPackages
Include packages not found on nuget server in the return object(s).
Bypass default Error output for missing packages.
Packages returned this way are duplicates of the package that was passed in, plus two members: IsValid=$false, Tier=Tier count + 1
.PARAMETER UseV2PackageMetadata
Use the new pacakge metadata v2 methods
.PARAMETER PackageToServerMap
Optional map of packages to servers. Useful in TeamCity.Deployment.Code for determining if a package is missing from the servers in the environment
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[AllowNull()]
[object[]]$ChocoPackages,
[Parameter(Mandatory=$true)]
[string[]]$Servers,
[Parameter(Mandatory = $false)]
[switch]$FilterFeeds,
[Parameter(Mandatory = $false)]
[switch]$FilterPowerShellModules,
[Parameter(Mandatory=$true)]
[PSCredential]$NugetCredential,
[Parameter(Mandatory=$false)]
[switch]$IncludeMissingPackages,
[Parameter(Mandatory=$false)]
[switch]$UseV2PackageMetadata,
[object]$PackageToServerMap
)
$loglead = Get-LogLeadName
if (!$ChocoPackages -or ($ChocoPackages.Count -eq 0)) {
return $null
}
# Determine what types of servers are in the environment.
$webServers = Select-AlkamiWebServers $Servers
$appServers = Select-AlkamiAppServers $Servers
$micServers = Select-AlkamiMicServers $Servers
$fabServers = Select-AlkamiFabServers $Servers
$hasWebServers = $null -ne $webServers
$hasAppServers = $null -ne $appServers
$hasMicServers = $null -ne $micServers
$hasFabServers = $null -ne $fabServers
# Select the first server to grab configured feeds from.
$firstServer = $Servers | Select-Object -First 1
# Verify that all of the packages have feeds set, to make the appropriate proget API calls.
if (([array]($ChocoPackages | Where-Object { $null -eq $_.Feed })).Count -gt 0) {
Set-ChocoPackageSourceFeedsV2 -Packages $ChocoPackages -Hostname $firstServer
}
# Filter out any packages that come from the default chocolatey feed, nuget.internal, or the SRE tools feed.
if ($filterFeeds.IsPresent) {
$ChocoPackages = $ChocoPackages | Where-Object { (!$_.Feed.IsDefault) -and ($_.Feed.Source -notlike "*nuget/nuget.internal*") -and ($_.Feed.Source -notlike "*SRETools*") }
}
# Filter out PowerShell modules.
if ($filterPowerShellModules.IsPresent -and ($null -ne $chocoPackages)) {
# Test-IsPackagePowerShellModule tests against a global in \Alkami.PowerShell.Choco\Private\VariableDeclarations.ps1
$ChocoPackages = $ChocoPackages | Where-Object { !(Test-IsPackagePowerShellModuleV2 -PackageName $_.Name) }
}
# Fetch package metadata for each package.
# Parallelize this because it is making calls against Proget.
# TODO: Refactor this to use a splatted var
# Force the results into an array. Necessary for unit testing, and prevents PS unilaterally unboxing for no raisin.
$categorizedPackages = @()
$categorizedPackages += Invoke-Parallel -objects $ChocoPackages -returnObjects -arguments ($NugetCredential, $UseV2PackageMetadata, $hasMicServers, $hasFabServers) -script {
param($package, $arguments)
$NugetCredential = $arguments[0]
$micServersPresent = $arguments[2]
$fabServersPresent = $arguments[3]
# Fetch metadata for the package.
$categorizedPackage = Get-PackageMetadataV2 -Package $package -NugetCredential $NugetCredential -MicroserviceTierPresent:$micServersPresent -ServiceFabricTierPresent:$fabServersPresent
# Return the results in a map so we can separate the real result from needless PSJob properties (such as RunspaceId) on every package.
return @{ Package = $categorizedPackage; ValidPackage = $categorizedPackage.IsValid; }
}
$badPackages = $categorizedPackages | Where-Object { $_.ValidPackage -ne $true }
[string]$errorText = $null
if (!(Test-IsCollectionNullOrEmpty $badPackages)) {
if ($IncludeMissingPackages) {
Write-Host "$logLead : Resultset will be returned with Missing Packages included."
} else {
foreach ($badPackage in $badPackages) {
$errorText += "$($badPackage.Package.Name)|$($badPackage.Package.Version)|$($badPackage.Package.Feed.Source)`n"
}
Write-Error "$logLead : These Packages Were not found: `n $errorText"
}
}
# retrieve just the packages and discard the other information
$categorizedPackages = $categorizedPackages | ForEach-Object { $_.Package }
# Run back through each package and figure out where the package should be installed.
foreach ($package in $categorizedPackages) {
# This didn't pick up like I expected it to
$package | Add-Member -NotePropertyName "Tier" -NotePropertyValue (-1) -Force
# ensure if we chose that we should do a force-install that we stick to the types of servers present
# as-is, a package can be marked "force install" to both tiers if neither tier can be determined correctly
if ($package.ForceInstallToWebDetermination) {
$package.InstallToWeb = $package.InstallToWeb -and $hasWebServers
}
if ($package.ForceInstallToAppDetermination) {
$package.InstallToApp = $package.InstallToApp -and $hasAppServers
}
# we don't need this loop because all this should be happening in Get-PackageMetadata
Write-Host "$loglead : Skipping re-categorization for [$($package.Name)]"
continue
}
if (!(Test-IsCollectionNullOrEmpty $PackageToServerMap)) {
# Since we have some packages that can be mapped, let's find out if they are on all the servers
Write-Host "$logLead : Testing each package for correct server count in PackageToServerMap hashtable"
foreach ($package in $categorizedPackages) {
$lowerName = $package.Name.ToLower()
$packageMap = $PackageToServerMap[$lowerName]
if (!(Test-IsCollectionNullOrEmpty $packageMap)) {
# The value in the map is a non-empty array
if ($package.InstallToWeb -and $hasWebServers) {
# figure out which servers are not in the list
foreach ($server in $webServers) {
if ($packageMap -notcontains $server) {
Write-Host "$logLead : Could not find server [$server] in the packageMap for package [$lowerName] - package missing from server."
if ($null -eq $package.MissingFromServers) {
$package.MissingFromServers = @()
}
$package.MissingFromServers += $server
$package.IsMissingFromServers = $true
}
}
}
if ($package.InstallToApp -and $hasAppServers) {
# figure out which servers are not in the list
foreach ($server in $appServers) {
if ($packageMap -notcontains $server) {
Write-Host "$logLead : Could not find server [$server] in the packageMap for package [$lowerName]. Package missing from server."
if ($null -eq $package.MissingFromServers) {
$package.MissingFromServers = @()
}
$package.MissingFromServers += $server
$package.IsMissingFromServers = $true
}
}
}
if ($package.InstallToMic -and $hasMicServers) {
# figure out which servers are not in the list
foreach ($server in $micServers) {
if ($packageMap -notcontains $server) {
Write-Host "$logLead : Could not find server [$server] in the packageMap for package [$lowerName]. Package missing from server."
if ($null -eq $package.MissingFromServers) {
$package.MissingFromServers = @()
}
$package.MissingFromServers += $server
$package.IsMissingFromServers = $true
}
}
}
}
}
}
# Get the microservice tiers.
$tiers = Get-MicroserviceTiers
# Get a [packageName -> tier] mapping of the tiers.
$tierMap = @{}
for ($tier = 0; $tier -lt $tiers.Count; $tier++) {
$tierPackageNames = $tiers[$tier]
foreach ($packageName in $tierPackageNames) {
$tierMap[$packageName] = $tier
}
}
$catchAllTier = $tiers.Count
# Assign each package to a tier.
foreach ($package in $categorizedPackages) {
# Assign installers to the first tier.
$tier = -1
if ($package.IsInstaller) {
$tier = 0
} elseif ($package.IsComponentizedWebApp) {
$tier = 1
} else {
# Check if the package is specifically named as being in a tier.
if ($tierMap.ContainsKey($package.Name)) {
$tier = $tierMap[$package.Name]
} elseif ($package.IsValid) {
# Put it in the last tier.
$tier = $catchAllTier
} else {
# Missing(invalid) packages get (Number of Tiers + 1) so that
# The sorting expression will give them a negative number so
# that they are _obviously_ invalid
$tier = $catchAllTier + 1
}
}
$package.Tier = $tier
}
# FUTURE US SRE-16834 Hotfix Tiering
# We may want to set all hotfixes to $tiers.Count + 1. This is a good place for that.
# Sort packages.
# Tiers -> Installers -> Infrastructure Microservices -> Microservices -> Not-Microservices -> SDK
# Powers of 2 guarantee that the higher-precedent conditions will always take priority in sorting.
# The "catchAllTier - tier" is to make the tiers sort in ascending order. catchAllTier = tiers.count
$categorizedPackages = ($categorizedPackages | Sort-Object -Property @{
Expression = {
16 * [int]($catchAllTier - $_.Tier) +
8 * [int]($_.IsInstaller) +
4 * [int]($_.IsInfrastructure) +
2 * [int]($_.IsMicroservice) +
1 * [int](!($_.IsSDK))
}
} -Descending)
return $categorizedPackages
}