304 lines
15 KiB
PowerShell
304 lines
15 KiB
PowerShell
|
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`".";
|
|||
|
}
|