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

431 lines
22 KiB
PowerShell
Raw Permalink Normal View History

2023-05-30 22:51:22 -07:00
function Get-PackageMetadataV2 {
<#
.SYNOPSIS
Returns the package object with metadata attached about what the package is. This does not modify the original $package object.
.PARAMETER Package
An object originating in Format-ParseChocoPackages that has an assigned feed and other details
.PARAMETER NugetCredential
If the feed we are talking to needs credentials, those should be supplied here
Notably this was historically used for SDK feeds, but may be used in the future for other feeds
This is because the SDK feeds should have been moved to a more accessible, non-authorization based feed for speed
.OUTPUTS
Returns a Package object (unless the input object was null, then we return null)
The returned Package object should include additional details if it has been categorized
#>
[CmdletBinding()]
[OutputType([object])]
Param (
[Parameter(Mandatory = $true)]
[AllowNull()]
[object]$Package,
[Parameter(Mandatory = $true)]
[PSCredential]$NugetCredential,
[switch]$MicroserviceTierPresent,
[switch]$ServiceFabricTierPresent
)
$loglead = Get-LogLeadName
#region Guard clauses
if ($null -eq $Package) {
return $null
}
# Verify that all of the packages have feeds set, to make the appropriate proget API calls.
if (($null -eq $Package.Feed) -or ([string]::IsNullOrWhiteSpace($Package.Feed.Source))) {
Write-Error "$loglead : Package $($Package.Name) is missing a feed source. Please make sure that you have executed Set-ChocoPackageSourceFeedsV2 against the package"
return $Package
}
Write-Host "$loglead : Now looking up information for $($Package.Name)|$($Package.Version) in $($Package.Feed.Source)"
#endregion Guard clauses
# Replace the name of the package with the name (and casing) from the nuspec. This solves casing consistency problems.
$nuspec = Get-PackageNuspecXmlV2 -Package $Package -Credential $NugetCredential
if ($null -eq $nuspec) {
# If we can't get the nuspec, this isn't a package we can change much about. Must be public?
Write-Host "$logLead : Could not find a nuspec for [$($Package.Name)], continuing"
return $Package
}
$Package.Name = $nuspec.package.metadata.id
# Now that we have gotten this far, we have a valid package
$Package.IsValid = $true
# Query for the files in the package for re-use.
$packageFiles = Get-PackageFileListV2 -Package $Package -Credential $NugetCredential
# Some values can be calculated for all packages independently, some require package knowledge more than just files
$isSdk = if ($null -eq $Package.Feed.Source) { $false } else { $Package.Feed.IsSDK }
## Functionality not supported by manifests, driven by global variables
# Define packages that must only be installed in upgrade-mode.
$upgradeOnly = Test-IsPackageUpgradeOnlyV2 -Package $Package
$hasInfrastructureMigrations = Test-PackageHasInfrastructureMigrationsV2 -PackageFiles $packageFiles
$isInfrastructure = Test-IsPackageInfrastructureMicroserviceV2 -Package $Package
$isReliableService = Test-IsPackageReliableServiceV2 -PackageFiles $packageFiles -Package $Package
$isComponentizedWebApp = Test-IsPackageComponentizedWebApplication -Package $Package
$tags = ($nuspec.Package.Metadata.Tags -split " ").Where({ ![string]::IsNullOrWhiteSpace($_) })
# Default values
$applicationTypeName = $null
$newRelicAppName = $null
$hasAlkamiManifest = $false
$componentType = $null
$isDbms = $false
$hasDatabaseConfigFile = $false
$isMigrationPackage = $false
$manifestRuntime = ""
$isMicroservice = $false
$isInstaller = $false
$isAppTierWebApplication = $false
$isWebTierWebApplication = $false
$isPowerShellModule = $false
$isFullScaleMicroservice = $false
$installToWeb = $false
$installToApp = $false
$installToMic = $false
$installToFab = $false
$webAllowListed = $false
$appAllowListed = $false
$isReportPackage = $false
$isHotfixPackage = $false
$hasMigrations = $false
# We need a way to maintain this deep into the heirarchy well after classification, so adding it to the package seemed appropriate
$hotfixFixedInOrbVersion = ""
# Added to support hotfixes potentially not being uninstalled with scripts.
# Look for this to get updated in the Classify_Packages and honored in Install_Packages sections
$skipUninstallScripts = $false
# This is here to provide an easier way to infer the data elsewhere in the pipeline.
# See also: Remove-PackagesThatAreAlreadyInstalled
# Additionally, this function is the source-of-truth for how-it-should-be, so documentation as code is good.
$mustReinstallForOrbInstall = $false
# we had an old toggle-setting problem that default installed packages to app,
# so we should force set to app tier if we don't have a tier _and_ it's a legacy classification
$usingLegacyValidation = $false
$forceToAppTier = $false
$forceToWebTier = $false
#region This is where we decide if we have a file, and then make the results off of that
if ((Test-PackageHasAlkamiManifestV2 -PackageFiles $packageFiles)) {
$hasAlkamiManifest = $true
$alkamiManifest = Get-PackageAlkamiManifestV2 -Package $Package -Credential $NugetCredential
$componentName = $alkamiManifest.general.element
$componentType = $alkamiManifest.general.componentType
Write-Host "$logLead : Categorizing [$($Package.Name)|$componentName] as a [$componentType] based on AlkamiManifest"
switch ($componentType) {
'WebApplication' {
# client web app (think CUFX, Isotope)
if ((Get-ValidWebTierInstallLocations) -contains $alkamiManifest.webApplicationManifest.appInstall) {
Write-Host "$logLead : WebApplication [$componentName] determined to be Web-tier"
$isWebTierWebApplication = $true
$installToWeb = $true
}
# WCF web app (think RPSTS)
if (@('Legacy') -contains $alkamiManifest.webApplicationManifest.appInstall) {
Write-Host "$logLead : WebApplication [$componentName] determined to be App-tier"
$isAppTierWebApplication = $true
$installToApp = $true
}
}
'WebSite' {
# Websites are indistinguishable as they could be intended for web or app tiers
# Since they have to be mapped to from nginx or similar, and as they are few and far between, we just install them everywhere for now
# TODO: Add WebSite manifest concept for tiering (ex: CoreDashboard runs on the app tier)
$installToApp = $true
$installToWeb = $true
Write-Host "$logLead : Website [$componentName] found, categorized to go on both web and app tier"
}
'Widget' {
$installToWeb = $true
$mustReinstallForOrbInstall = $true
}
'WebExtension' {
$installToWeb = $true
$mustReinstallForOrbInstall = $true
}
'SREModule' {
$isPowerShellModule = $true
}
'Report' {
# reports should do nothing, so don't set any environment flags
$isReportPackage = $true
# In case we determine we should uninstall it, just rapidly get it off the servers
$skipUninstallScripts = $true
}
'Service' {
$isMicroservice = $true
$isDbms = Test-ServiceManifestRequiresDbAccess -ServiceManifest $alkamiManifest.serviceManifest
$manifestRuntime = Get-ValidatedRuntimeParameter -Runtime $alkamiManifest.serviceManifest.runtime
Write-Host "$logLead : Found service [$componentName] should $(if ($isDbms){}else{"not "} )run as a database capable user"
$hasMigrations = Test-ServiceManifestHasMigrations -ServiceManifest $alkamiManifest.serviceManifest
Write-Host "$logLead : Found service [$componentName] should $(if ($hasMigrations){}else{"not "} )run migrations"
}
'Provider' {
$installToApp = $true
$mustReinstallForOrbInstall = $true
}
'Installer' {
$isInstaller = $true
}
'FluentMigration' {
$isMigrationPackage = $true
$hasMigrations = $true
$manifestRuntime = Get-ValidatedRuntimeParameter -Runtime $alkamiManifest.fluentMigrationManifest.runtime
}
'NodeMigration' {
# This package type is here to demonstrate adding support for node based migration packages
$isMigrationPackage = $true
$hasMigrations = $true
throw "$logLead : There is no support for NodeMigrations yet. Please talk to SRE about removing this throw if you believe NodeMigrations should be supported in your environment."
}
'LegacyUtility' {
# The type of utilities typically represented by this installer
# have historically only gone to the app tier, where the WCF services are
$installToApp = $true
}
'Hotfix' {
# Hotfixes can go to APP OR WEB OR ALL
$isHotfixPackage = $true
$hotfixFixedInOrbVersion = $alkamiManifest.hotfixManifest.fixedInOrbVersion
$mustReinstallForOrbInstall = $true
$hotfixServerTier = $alkamiManifest.hotfixManifest.serverTier
# TODO: Will need to be revisited after 2022.4 release in the future (see also Get-PackageMetadataV2)
# Original 1.0 manifests did not have serverTier
# Null-or-Whitespace will mean Install-to-All-tiers until 2022.4 when hotfixes have serverTier node
if (Test-StringIsNullOrWhiteSpace -Value $hotfixServerTier) {
Write-Warning "$logLead : MISSING NODE - HOTFIX MANIFEST - serverTier"
Write-Warning "$logLead : Temporarily forcing this to install to ALL serverTiers"
Write-Warning "$logLead : This will be REMOVED in the NEAR future"
$hotfixServerTier = "ALL"
}
$installToApp = $hotfixServerTier -in ("APP", "ALL")
$installToWeb = $hotfixServerTier -in ("WEB", "ALL")
# SRE-18492 - For Zero-downtime releases of ORB, where we are deploying FULL-ORB via a HOTFIX package
# - don't give me that look -
# we need to keep MICs in sync with every other host in the designation
# for that reason, we will keep MICs in sync with APPs
# we could be cute here and only do this when it's going to "ALL" or both tiers
# but cute is as bad as clever
# we could also simply set $installToMic, but I want it to be explicit and obvious below
# This will be used below where $installToMic gets set
$isHotfixInstallToApp = $installToApp
}
'ApiComponent' {
# Api Components always go to web, that's the business rule
$installToWeb = $true
}
}
} else {
Write-Host "$logLead : Categorizing [$($Package.Name)] based on legacy criteria"
$usingLegacyValidation = $true
# do the legacy checks that were developed over years of misconfiguration
# Define defaults for types of services/installations.
# Functions that specify -Credential are querying out to proget.
$isMicroservice = Test-IsPackageMicroserviceV2 -NuspecXmlObject $nuspec -Package $Package
$isInstaller = Test-IsPackageInstallerV2 -Package $Package
$isPowerShellModule = Test-IsPackagePowerShellModuleV2 -Package $Package
$installToWeb = ($_WebNuspecTags.Where({ $tags -contains $_ }).Count -gt 0)
$installToApp = ($_AppNuspecTags.Where({ $tags -contains $_ }).Count -gt 0)
$webAllowListed = $_WebAllowList -contains $Package.Name
$appAllowListed = $_AppAllowList -contains $Package.Name
# If it's a microservice see if it's fullscale, etc
if ($isMicroservice) {
$isFullScaleMicroservice = Test-IsPackageFullScaleMicroserviceV2 -PackageName $Package.Name
$isDbms = Test-IsPackageDbmsV2 -NuspecXmlObject $nuspec
$hasDatabaseConfigFile = Test-PackageHasDatabaseConfigFile -PackageFiles $packageFiles
$hasMigrations = $isDbms -and $hasDatabaseConfigFile
}
}
#endregion This is where we decide if we have a file, and then make the results off of that
# Some things have to be done once we have the categorized data no matter what
# If it's a microservice fetch the new relic app name
# This applies whether we have the manifest or not, same operation applies
if ($isMicroservice -or $isWebTierWebApplication -or $isAppTierWebApplication) {
$packagePaths = @($packageFiles.Where({ $_.name -eq "$($Package.Name).exe.config" }).fullPath)
if ($packagePaths.Count -eq 0) {
Write-Host "$logLead : Could not find a [$($Package.Name).exe.config] in proget, this is probably all fine and normal. Just means we can't set the NewRelic.AppName variable."
continue
}
$packagePath = $packagePaths[0]
# in the case of more than
if ($packagePaths.Count -gt 1) {
# Find the one with the least number of splits (closest to the parent of the folder)
$packagePath = ($packagePaths | Sort-Object -Property @{Expression = { ($_ -split '/').Count }; Descending = $False } | Select-Object -First 1)
}
if (![string]::IsNullOrWhiteSpace($packagePath)) {
Write-Host "$logLead : Looking for appsettings for [$($Package.Name)] at [$packagePath]"
$appSettingsXml = [xml](Get-PackageFileV2 -Package $Package -Credential $NugetCredential -PackagePath $packagePath)
$newRelicAppName = Get-AppSetting -Key "NewRelic.AppName" -XmlDocument $appSettingsXml
}
}
if ($isReliableService) {
$applicationTypeName = Get-AlkamiServiceFabricPackageServiceTypeName -source $Package.Feed.Source -name $package.Name -version $package.Version -nugetCredential $NugetCredential
}
#region Determine tiering
# Figure out if it's something that goes on every server.
$goesEverywhere = $isInfrastructure -or $isInstaller -or $isPowerShellModule
# Force the upgrade in certain scenarios, to ensure things are re-registered
# That is NOT what this does. This does NOT force anything
# This will NOT cause ANYTHING to be re-registered
# This is ONLY used in this file when negated - to set the "ForceSameVersion" member and
# not negated to set a member that is never used again, "Upgrade"
# "ForceSameVersion" is only used in Orb Installs - meaning jobs where "ForceReinstallPackages" is true
#
# So...
# Microservices do not need to be ForceSameVersion/ForceReinstallPackages - they do not get deleted with ORB
# Installers - same thing
# UpgradeOnly? That's just VariableDeclarations.ps1 -> $_UpgradePackages
# - PSModules, CoreDashboard, EagleEye, newrelic. Stuff well away from ORB folders
# Basically, this variable is "stuff that isn't blown away by an ORB deploy"
$isSafeFromOrbDeploys = $isMicroservice -or $upgradeOnly -or $isInstaller
$isDeletedByOrbDeploys = -NOT $isSafeFromOrbDeploys
# appAllowListed means it is absolutely set to go to the app tier, which means it can't go to the web tier
# webAllowListed conversely means it can't go to the app tier
# In either case, if those are set to true, the corresponding tier must be set to false
# Example cases of these values being set are known SDK client packages that have odd or miscategorized names
$installToWeb = ($goesEverywhere -or $isWebTierWebApplication -or $installToWeb -or $webAllowListed)
$installToApp = ($goesEverywhere -or $isAppTierWebApplication -or $installToApp -or $appAllowListed -or $isFullScaleMicroservice)
$installToMic = ($goesEverywhere -or $installToMic -or $isMicroservice -or $isFullScaleMicroservice -or $isHotfixInstallToApp)
$installToFab = ($goesEverywhere -or $installToFab -or $isReliableService -or $isFullScaleMicroservice)
# If it's a service fabric server, we should put the microservice tier packages there
if ($ServiceFabricTierPresent) {
$installToFab = ($installToFab -or $installToMic)
$installToMic = $false
} else {
$installToFab = $false
}
if (!$MicroserviceTierPresent) {
# If there's no microservice tier, whatever was gonna go on mics should goto apps
$installToApp = $installToApp -or $installToMic
$installToMic = $false
}
if ($usingLegacyValidation -and !$installToWeb -and !$installToApp -and !$installToMic -and !$installToFab) {
# we had an old toggle-setting problem that default installed packages to app,
# so we should force set to web and app tiers if we don't have a tier _and_ it's a legacy classification
$forceToAppTier = $true
$forceToWebTier = $true
}
#endregion Determine tiering
#region Build the output object
# Create a new package object, and copy over all of the existing data.
$newPackageData = @{ }
foreach ($property in $Package.psobject.properties.name) {
$newPackageData[$property] = $Package.$property
}
# Store classification metadata.
$newPackageData["IsSDK"] = $isSdk
$newPackageData["IsHotfix"] = $isHotfixPackage
if ($isHotfixPackage) {
$newPackageData["HotfixFixedInOrbVersion"] = $hotfixFixedInOrbVersion
$newPackageData["ServerTier"] = $hotfixServerTier
}
$newPackageData["SkipUninstallScripts"] = $skipUninstallScripts
$newPackageData["HasAlkamiManifest"] = $hasAlkamiManifest
if ($hasAlkamiManifest) {
$newPackageData["ComponentType"] = $componentType
}
$newPackageData["IsInstaller"] = $isInstaller
$newPackageData["IsReportPackage"] = $isReportPackage
$newPackageData["IsInfrastructure"] = $isInfrastructure
$newPackageData["IsComponentizedWebApp"] = $isComponentizedWebApp
$newPackageData["NewRelicAppName"] = $newRelicAppName
$newPackageData["Tags"] = $tags
# Some things have to be reinstalled if we reinstall ORB legacy
if ($mustReinstallForOrbInstall) {
$newPackageData["ReinstallWithORB"] = $mustReinstallForOrbInstall
}
# x - Track things that are only installed when upgrading
# This ^ is not worded well. This is things that are not blown away by an ORB deploy
# This is also NOT USED anywhere.
$newPackageData["Upgrade"] = $isSafeFromOrbDeploys
# Reinstall the things that get blown away by ORB deploys
$newPackageData["ForceSameVersion"] = $isDeletedByOrbDeploys
$newPackageData["PreventRollback"] = $isInstaller
$newPackageData["HasInfrastructureMigration"] = $hasInfrastructureMigrations
# Store database related metadata
$newPackageData["HasMigrations"] = $hasMigrations
$newPackageData["IsDbms"] = $isMicroservice -and $isDbms
$newPackageData["IsMigrationPackage"] = $isMigrationPackage
# SRE-16977 MigrationRunner utility required metadata
if ($hasAlkamiManifest -and ($isDbms -or $isMigrationPackage)) {
$newPackageData["ManifestRuntime"] = $manifestRuntime
$newPackageData["MigrationRunnerPath"] = Get-MigrationRunnerExe -Runtime $manifestRuntime
}
# Store microservice related metdata
$newPackageData["IsMicroservice"] = $isMicroservice
$newPackageData["IsFullScaleMicroservice"] = $isFullScaleMicroservice
# Add Service Fabric metadata.
$newPackageData["IsReliableService"] = $isReliableService
if (![string]::IsNullOrWhiteSpace($applicationTypeName)) {
$newPackageData["ApplicationTypeName"] = $applicationTypeName
}
# Store Install-To-Tier Metadata
$newPackageData["InstallToWebTier"] = $installToWeb -or $forceToWebTier
$newPackageData["InstallToAppTier"] = $installToApp -or $forceToAppTier
# we had an old toggle-setting problem that default installed packages to app,
# so we should force set to web and app tiers if we don't have a tier _and_ it's a legacy classification
# see code above
$newPackageData["ForceInstallToWebTierDetermination"] = $forceToWebTier
$newPackageData["ForceInstallToAppTierDetermination"] = $forceToAppTier
$newPackageData["InstallToMicTier"] = $installToMic
# This categorization is bypassed in the Get-PackageInstallationData step, so we move it here.
$newPackageData["InstallToWeb"] = $installToWeb -or $forceToWebTier
$newPackageData["InstallToApp"] = $installToApp -or $forceToAppTier
# This information should be populated by a process that knows about what servers and packages are in an environment
# The "IsMissingFromServers" should be set to bool based on the above information by whoever sets that value
# The properties are defined here, however, as "class definition" so we know where they are initialized from when searching the codebase.
$newPackageData["IsMissingFromServers"] = $null
$newPackageData["MissingFromServers"] = @()
# we had an old toggle-setting problem that default installed packages to app,
# so we should force set to web and app tiers if we don't have a tier _and_ it's a legacy classification
# see code above
$newPackageData["ForceInstallToWebDetermination"] = $forceToWebTier
$newPackageData["ForceInstallToAppDetermination"] = $forceToAppTier
$newPackageData["InstallToMic"] = $installToMic
# There are no dynamically determined fabric clusters anymore. We know where we are intentionally deploying to fab instead of mic.
$newPackageData["InstallToFab"] = $installToFab
$newPackageData["Tier"] = -1
# Create the new package object and return;
$newPackage = New-Object -TypeName PSObject -Prop $newPackageData
return $newPackage
#endregion Build the output object
}