ps/Modules/Alkami.PowerShell.ServiceFabric/Public/New-AlkamiServiceFabricReliableServicePackage.ps1

304 lines
15 KiB
PowerShell
Raw Permalink Normal View History

2023-05-30 22:51:22 -07:00
function New-AlkamiServiceFabricReliableServicePackage {
<#
.SYNOPSIS
Prepares ReliableService implementations for deployment into a target environment.
Note: The output folder is -not- automatically cleaned up! Delete it so avoid piling up memory.
.PARAMETER source
The nuget URL of the package repository.
.PARAMETER name
The name/ID of the package to be created.
.PARAMETER version
The version of the package to be created.
.PARAMETER userMicro
The name of the non-database microservice user gmsa account.
.PARAMETER userDbms
The name of the database microservice user gmsa account.
.PARAMETER defaultInstanceCount
The default number of microservice instances that will be deployed.
.PARAMETER outputFolder
The download/output directory of the package.
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true)]
[Alias("s")]
[string]$source,
[Parameter(Mandatory = $true)]
[Alias("n")]
[string]$name,
[Parameter(Mandatory = $true)]
[Alias("v")]
[string]$version,
[Parameter(Mandatory = $true)]
[string]$environmentType,
[Parameter(Mandatory = $true)]
[string]$userMicro,
[Parameter(Mandatory = $true)]
[string]$userDbms,
[Parameter(Mandatory = $true)]
[int]$defaultInstanceCount,
[Parameter(Mandatory = $true)]
[Alias("o")]
[string]$outputFolder,
[Parameter(Mandatory = $false)]
[string]$defaultLogConfigFolder = $null,
[Parameter(Mandatory = $false)]
[switch]$force,
[Parameter(Mandatory = $false)]
[pscredential]$nugetCredential = $null
)
$loglead = Get-LogLeadName;
$packageString = "$name|$version";
Write-Host "$loglead : Creating Service Fabric package for $packageString";
# Download Alkami package.
if(Test-Path $outputFolder) {
if(!$force) {
throw "$loglead : Directory `"$outputFolder`" already exists. If you intend to overwrite it use -force.";
} else {
Write-Host "$loglead : Recreating directory $outputFolder";
Remove-Item -Path $outputFolder -Recurse -Force;
New-Item -Path $outputFolder -ItemType Directory | Out-Null;
}
} else {
Write-Host "$loglead : Creating directory $outputFolder";
New-Item -Path $outputFolder -ItemType Directory | Out-Null;
}
$tempPackagePath = (Join-Path $outputFolder "nugetDownload.zip");
try {
# Download specified package from nuget feed.
Write-Verbose "$loglead : Downloading package $packageString from proget to `"$tempPackagePath.`"";
Get-PackageFromProget -source $source -name $name -version $version -output $tempPackagePath -nugetCredential $nugetCredential;
$serviceName = "svc";
# Note: The service name "svc" is for short service fabric pathing. This is not actually the name of the service.
# The full name of the service will look like this: "Alkami.Widget.Sample/v1/svc/"
# Unzip the .nupkg so we can manipulate manifests.
Write-Verbose "$loglead : Unzipping $packageString from `"$tempPackagePath`" to `"$outputFolder`"";
$szPath = Get-7ZipPath
& $szPath x "$tempPackagePath" -aoa -o"$outputFolder" "*" -r | Out-Null
# Clean up nuget .nupkg download.
if(Test-Path $tempPackagePath) {
Remove-Item -Path $tempPackagePath;
}
$codeRoot = Join-Path $outputFolder "svc\Code"
# Make sure that this is actually a reliable service.
$files = Get-ChildItem $outputFolder -Include "*.xml" -Recurse;
$applicationConfigPath = ($files | Where-Object {$_.Name -eq "ApplicationManifest.xml"}) | Select-Object -First 1;
$serviceConfigPath = ($files | Where-Object {$_.Name -eq "ServiceManifest.xml"}) | Select-Object -First 1;
$hasApplicationManifest = $null -ne $applicationConfigPath;
$hasServiceManifest = $null -ne $serviceConfigPath;
if(!($hasApplicationManifest -and $hasServiceManifest)) {
throw "$loglead : $name-$version is not a valid Service Fabric Reliable Service";
}
# Determine if the config defaults folder has been configured.
$hasConfigDefaults = (!([string]::IsNullOrWhiteSpace($defaultLogConfigFolder))) -and (Test-Path $defaultLogConfigFolder)
# If the config defaults folder was specified, see if the microservice should have new relic enabled/disabled.
if($hasConfigDefaults) {
# Read in the package names to leave new-relic enabled for.
$newRelicServicePath = (Join-Path $defaultLogConfigFolder "NewRelicMicroServices/NewRelicMicroServices.txt")
$newRelicMicroservicesToLeaveEnabled = $null
if(Test-Path $newRelicServicePath) {
$newRelicMicroservicesToLeaveEnabled = [array](Get-Content -Path $newRelicServicePath)
} else {
Write-Warning "$loglead : Could not find NewRelicMicroServices.txt to leave new relic enabled. Assuming no microservices should have NewRelic enabled."
$newRelicMicroservicesToLeaveEnabled = $null
}
$enableNewRelic = ($newRelicMicroservicesToLeaveEnabled -contains $name)
Set-ChocolateyPackageNewRelicState -Directory $codeRoot -Enabled $enableNewRelic
}
# If the log4net config defaults folder was specified, look for a config for this service.
if($hasConfigDefaults) {
Write-Verbose "$loglead : Config default folder exists, looking for $name log4net default."
$configDefaultsLog4Net = (Join-Path $defaultLogConfigFolder "log4net")
$logConfigPath = Get-DefaultLog4NetPathForPackage -SourcePath $configDefaultsLog4Net -PackageName $name -EnvironmentName $environmentShortName -EnvironmentType $environmentType -IsReliableService
Write-Verbose "$loglead : Looking for log4net default config path at '$logConfigPath'"
# If the log4net config path exists for the service we're deploying, replace the one in the package!
if ((!([String]::IsNullOrEmpty($logConfigPath))) -and (Test-Path $logConfigPath)) {
$destinationLog4NetPath = (Join-Path $codeRoot "log4net.config")
Write-Verbose "$loglead : Log4net config default replacement exists, replacing the config from the package."
Copy-Item -Path $logConfigPath -Destination $destinationLog4NetPath -Force
} else {
Write-Verbose "$loglead : There is no log4net config default. Using the log4net.config from the package."
}
}
# Determine the number of instances to run for a service. -1 means it runs on every node.
$instanceCount = $defaultInstanceCount;
$everywhereServices = $Global:InfrastructureMicroServices;
if($null -ne ($everywhereServices | Where-Object {$_ -like $name})) {
$instanceCount = -1;
}
# Detect which user to run the application as by reading the nuspec for a dependency to the database migrator.
$nuspecPath = (Get-ChildItem -Path $codeRoot -Filter "*.nuspec" | Select-Object -First 1).FullName;
$hasMigrations = $true;
if(($null -ne $nuspecPath) -and (Test-Path $nuspecPath)) {
$nuspec = [xml](Get-Content -Path $nuspecPath);
$hasMigrations = (Test-IsPackageDbms -nuspec $nuspec);
$nuspec = $null;
}
# Else we assume the package has migrations just to be conservative.
$gmsaUser = if($hasMigrations) { $userDbms; } else { $userMicro; }
# Fill out the application manifest.
Write-Verbose "$loglead : Creating Application Manifest at `"$applicationConfigPath`"";
$applicationManifest = (Read-XMLFile -xmlPath $applicationConfigPath);
$root = $applicationManifest.ApplicationManifest;
$root.SetAttribute("ApplicationTypeVersion", $version);
$root.ServiceManifestImport.ServiceManifestRef.SetAttribute("ServiceManifestName",$serviceName);
$root.ServiceManifestImport.ServiceManifestRef.SetAttribute("ServiceManifestVersion",$version);
$root.DefaultServices.Service.SetAttribute("Name", $serviceName);
$root.DefaultServices.Service.StatelessService.SetAttribute("InstanceCount", $instanceCount);
# Make sure that the reliable service is not deployed on the seed nodes.
$placementConstraintNode = $root.DefaultServices.Service.StatelessService.ChildNodes | Where-Object {$_.Name -eq "PlacementConstraints"} | Select-Object -First 1;
if($null -eq $placementConstraintNode) {
Write-Verbose "$loglead Could not find ApplicationManifest parameter PlacementConstraints, adding..";
$placementConstraintNode = $applicationManifest.CreateElement("PlacementConstraints");
[void]$root.DefaultServices.Service.StatelessService.AppendChild($placementConstraintNode);
}
$placementConstraintNode.InnerText = "NodeType != SeedNode";
# Set certificate properties.
$certificate = $null;
$reverseProxyUrl = $null;
$isDevEnvironment = ($environmentType -eq "Development");
$isQaEnvironment = ($environmentType -eq "QA");
$isStagingEnvironment = ($environmentType -eq "Staging");
$isProductionEnvironment = ($environmentType -eq "Production");
# Only modify the certificate properties if it is NOT a qa environment. QA environment properties should just be passed through.
# This should eventually change with certificate conventions defined for each environment.
if(!$isQaEnvironment) {
if($isDevEnvironment) {
$certificate = "*.dev.alkamitech.com";
$reverseProxyUrl = "https://microservices.dev.alkamitech.com:19081";
} elseif($isStagingEnvironment) {
$certificate = "*.staging.alkamitech.com";
$reverseProxyUrl = "https://microservices.staging.alkamitech.com:19081";
} elseif($isProductionEnvironment) {
$certificate = "api.alkami.com" # Has a SAN for tde.prod.alkami.net
$reverseProxyUrl = "https://tde.prod.alkami.net:19081"
}
# Throw an exception if the environment type did not successfully set a certificate to use / reverse proxyu URL.
if(($null -eq $certificate) -or ($null -eq $reverseProxyUrl)) {
throw "Unhandled environment type $environmentType. Certificate and reverse proxy url is undefined.";
}
# Set certificate common name.
$certNameNode = $root.Parameters.ChildNodes | Where-Object {$_.Name -eq "CertificateCommonName"} | Select-Object -First 1;
if($null -eq $certNameNode) {
Write-Verbose "$loglead Could not find ApplicationManifest parameter CertificateCommonName, adding..";
$certNameNode = $applicationManifest.CreateElement("Parameter");
[void]$root.Parameters.AppendChild($certNameNode);
}
$certNameNode.SetAttribute("Name", "CertificateCommonName");
$certNameNode.SetAttribute("DefaultValue", $certificate);
# Set JWT certificate name.
$jwtCertNameNode = $root.Parameters.ChildNodes | Where-Object {$_.Name -eq "JWTCertificateCommonName"} | Select-Object -First 1;
if($null -eq $jwtCertNameNode) {
Write-Verbose "$loglead Could not find ApplicationManifest parameter JWTCertificateCommonName, adding..";
$jwtCertNameNode = $applicationManifest.CreateElement("Parameter");
[void]$root.Parameters.AppendChild($jwtCertNameNode);
}
$jwtCertNameNode.SetAttribute("Name", "JWTCertificateCommonName");
$jwtCertNameNode.SetAttribute("DefaultValue", $certificate);
# Set the reverse proxy URL.
$reverseProxyNode = $root.Parameters.ChildNodes | Where-Object {$_.Name -eq "ReverseProxyUrl"} | Select-Object -First 1;
if($null -eq $reverseProxyNode) {
Write-Verbose "$loglead Could not find ApplicationManifest parameter ReverseProxyUrl, adding..";
$reverseProxyNode = $applicationManifest.CreateElement("Parameter");
[void]$root.Parameters.AppendChild($reverseProxyNode);
}
$reverseProxyNode.SetAttribute("Name", "ReverseProxyUrl");
$reverseProxyNode.SetAttribute("DefaultValue", $reverseProxyUrl);
}
# Set nuget package name as a property so we have a mapping from contract name -> nuget package name.
$nugetPackageNameNode = $root.Parameters.ChildNodes | Where-Object {$_.Name -eq "NugetPackageName"} | Select-Object -First 1;
if($null -eq $nugetPackageNameNode) {
Write-Verbose "$loglead Could not find ApplicationManifest parameter NugetPackageName, adding..";
$nugetPackageNameNode = $applicationManifest.CreateElement("Parameter");
[void]$root.Parameters.AppendChild($nugetPackageNameNode);
}
$nugetPackageNameNode.SetAttribute("Name", "NugetPackageName");
$nugetPackageNameNode.SetAttribute("DefaultValue", $name);
# Set the gmsa account to be used to run the service.
$serviceAccountNode = $root.Parameters.ChildNodes | Where-Object {$_.Name -eq "ServiceAccountToRunAs"};
if($null -eq $serviceAccountNode) {
throw "$loglead Could not find ServiceAccountToRunAs parameter. This must exist in the manifest!";
} else {
Write-Verbose "$loglead Setting ServiceAccountToRunAs parameter to $gmsaUser";
$serviceAccountNode.SetAttribute("DefaultValue", $gmsaUser);
}
# Make sure the gmsa policy section exists.
$policyNode = $root.ServiceManifestImport.Policies.RunAsPolicy;
if(($null -eq $policyNode) -or ($policyNode.UserRef -ne "DomainGMSA")) {
throw "$loglead Could not find gmsa RunAsPolicy section. This must exist in the manifest!";
}
# Set the environment type if there is one.
$environmentTypeNode = $root.Parameters.ChildNodes | Where-Object {$_.Name -like "*ASPNETCORE_ENVIRONMENT"};
if($null -ne $environmentTypeNode) {
Write-Verbose "$loglead Setting ASPNETCORE_ENVIRONMENT type setting to $environmentType"
$environmentTypeNode.SetAttribute("DefaultValue", $environmentType);
}
Save-XMLFile -xmlPath $applicationConfigPath -xml $applicationManifest;
# INFO: Powershell adds empty namespace attributes 'xmlns=""' to new elements. Remove those. There's probably a better way to do this.
(Get-Content -Path $applicationConfigPath).Replace("xmlns=`"`"","") | Set-Content -Path $applicationConfigPath;
$root = $null;
$applicationManifest = $null;
# Fill out the service manifest.
Write-Verbose "$loglead : Creating Service Manifest at `"$serviceConfigPath`"";
$serviceManifest = (Read-XMLFile -xmlPath $serviceConfigPath);
$root = $serviceManifest.ServiceManifest;
$root.SetAttribute("Name", $serviceName);
$root.SetAttribute("Version", $version);
$root.CodePackage.SetAttribute("Version", $version);
$root.ConfigPackage.SetAttribute("Version", $version);
Save-XMLFile -xmlPath $serviceConfigPath -xml $serviceManifest;
$root = $null;
$serviceManifest = $null;
} catch {
Write-Error "$loglead : Failed to create Service Fabric package:`n $($_.Exception.Message)";
# Clean up incomplete package build.
Start-Sleep -Seconds 1;
if(Test-Path $outputFolder) {
Write-Warning "$loglead : Removing incomplete package creation at $outputFolder";
Remove-Item $outputFolder -Recurse -Force;
}
}
Write-Host "$loglead : Package creation complete at `"$outputFolder`".";
}