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

281 lines
14 KiB
PowerShell
Raw Normal View History

2023-05-30 22:51:22 -07:00
function New-AlkamiServiceFabricPackage {
<#
.SYNOPSIS
Turns a Chocolatey/Nuget package into the file/folder input that Service Fabric requires for deployments.
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]$environmentName,
[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");
$fabricConfigTemplatesPath = (Join-Path $PSScriptRoot "ServiceFabricConfigTemplates");
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;
# Get a version of the microservice name with the "v3" style version name on the end.
$environmentShortName = Format-AlkamiEnvironmentName -name $environmentName;
$applicationTypeName = (Format-AlkamiServiceFabricApplicationName -name $name -environmentName $environmentName);
$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/"
# Create Service Fabric directory structure.
Write-Verbose "$loglead : Creating SF package directory structure in `"$outputFolder`"";
$subfolders = @(
$serviceName,
"$serviceName\Code",
"$serviceName\Config"
)
for ($i = 0; $i -lt $subfolders.Count; $i++) {
$path = (Join-Path $outputFolder $subfolders[$i]);
if (!(Test-Path $path)) {
New-Item -ItemType directory -Path $path -Force | Out-Null;
}
Write-Verbose "$loglead : Created $path";
$subfolders[$i] = $path;
}
$serviceRoot = $subfolders[0];
$codeRoot = $subfolders[1];
$configRoot = $subfolders[2];
# Unzip the .nupkg to where Service Fabric expects files to be.
Write-Verbose "$loglead : Unzipping $packageString from `"$tempPackagePath`" to `"$codeRoot`"";
$szPath = Get-7ZipPath
& $szPath x "$tempPackagePath" -aoa -o"$codeRoot" "tools/*" -r | Out-Null
& $szPath e "$tempPackagePath" -aoa -o"$codeRoot" "*.nuspec" -r | Out-Null
# Copy everything in the tools folder up a directory and remove it.
$toolsFolder = "$codeRoot/tools";
$items = Get-ChildItem -Path $toolsFolder;
foreach ($item in $items) {
Move-Item -Path $item.FullName -Destination $codeRoot;
}
Remove-Item $toolsFolder -Force;
# Clean up nuget .nupkg download.
if (Test-Path $tempPackagePath) {
Remove-Item -Path $tempPackagePath;
}
# Figure out if the service .exe exists before we proceed. We might be packaging up an orb provider.
$exeName = "$name.exe";
$exeLocation = (Join-Path $codeRoot $exeName);
if (!(Test-Path $exeLocation)) {
Write-Warning "$loglead : Could not locate standard $exeName name by convention. Falling back to searching for .exe's in the tools directory."
# Search the code root for a .exe.
$executableSearch = Get-ChildItem -Path $codeRoot -Filter "*.exe" | Where-Object { $_.Name -notlike "*.vshost.exe" };
if (($null -eq $executableSearch)) {
# Fail if we could not find any executable.
throw "$loglead : Could not locate microservice executable. Are you sure this is a microservice?";
} elseif ($executableSearch.Count -gt 1) {
# Fail if we found more than one executable in this folder.
throw "$loglead : Located more than one microservice executable. Cannot assume which executable to use. Investigate assumptions made.";
}
$exeLocation = $executableSearch.FullName
$exeName = $executableSearch.Name
Write-Verbose "$loglead : Found executable $exeName";
}
# 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 config defaults folder was specified, look for a log4net config for this microservice.
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
Write-Verbose "$loglead : Looking for log4net default config path at '$logConfigPath'"
# If the log4net config path exists for the microservice 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."
}
}
# Copy config template files to the appropriate locations.
$applicationConfigPath = (Join-Path $outputFolder "ApplicationManifest.xml");
$serviceConfigPath = (Join-Path $serviceRoot "ServiceManifest.xml");
$settingsConfigPath = (Join-Path $configRoot "Settings.xml");
Copy-Item -Path (Join-Path $fabricConfigTemplatesPath "ApplicationManifest.xml") -Destination $applicationConfigPath -Force
Copy-Item -Path (Join-Path $fabricConfigTemplatesPath "ServiceManifest.xml") -Destination $serviceConfigPath -Force
Copy-Item -Path (Join-Path $fabricConfigTemplatesPath "Settings.xml") -Destination $settingsConfigPath -Force
# 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);
$applicationManifestNS = $applicationManifest.DocumentElement.NamespaceURI
$root = $applicationManifest.ApplicationManifest;
$root.SetAttribute("ApplicationTypeName", $applicationTypeName);
$root.SetAttribute("ApplicationTypeVersion", $version);
$instanceCountNode = $root.Parameters.ChildNodes | Where-Object { $_.Name -eq "InstanceCount" };
$instanceCountNode.SetAttribute("DefaultValue", $instanceCount);
$root.ServiceManifestImport.ServiceManifestRef.SetAttribute("ServiceManifestName", $serviceName);
$root.ServiceManifestImport.ServiceManifestRef.SetAttribute("ServiceManifestVersion", $version);
$root.DefaultServices.Service.SetAttribute("Name", $serviceName);
$root.DefaultServices.Service.StatelessService.SetAttribute("ServiceTypeName", $serviceName);
$root.Principals.Users.User.SetAttribute("AccountName", $gmsaUser);
if ($null -ne $root.DefaultServices.Service.StatelessService.PlacementConstraints) {
$environmentWorkerNodeType = (Format-AlkamiEnvironmentWorkerNodeType $environmentName);
$root.DefaultServices.Service.StatelessService.PlacementConstraints = "NodeType == $environmentWorkerNodeType";
}
# Set nuget package name as a property.
$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",$applicationManifestNS);
[void]$root.Parameters.AppendChild($nugetPackageNameNode);
}
$nugetPackageNameNode.SetAttribute("Name", "NugetPackageName");
$nugetPackageNameNode.SetAttribute("DefaultValue", $name);
Save-XMLFile -xmlPath $applicationConfigPath -xml $applicationManifest;
$root = $null;
$applicationManifest = $null;
# Fill out the service manifest.
Write-Verbose "$loglead : Creating Service Manifest at `"$serviceConfigPath`"";
$serviceManifest = (Read-XMLFile -xmlPath $serviceConfigPath);
$serviceManifestNS = $serviceManifest.DocumentElement.NamespaceURI
$root = $serviceManifest.ServiceManifest;
$root.SetAttribute("Name", $serviceName);
$root.SetAttribute("Version", $version);
$root.ServiceTypes.StatelessServiceType.SetAttribute("ServiceTypeName", $serviceName);
$root.CodePackage.SetAttribute("Version", $version);
$root.ConfigPackage.SetAttribute("Version", $version);
$root.CodePackage.EntryPoint.ExeHost.Program = $exeName;
if ($exeName -eq "Alkami.Services.Subscriptions.Host.exe") {
$elem = $servicemanifest.CreateElement("ConsoleRedirection",$serviceManifestNS)
$root.CodePackage.EntryPoint.ExeHost.AppendChild($elem)
$elem.SetAttribute("FileRetentionCount", '5')
$elem.SetAttribute("FileMaxSizeInKb", '2048')
}
$root.Resources.Endpoints.Endpoint.SetAttribute("Name", "$($name).HostEndpoint");
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`".";
}