Conflicts:
	modules/InstallationSDK.ChefClientInstaller/InstallationSDK.ChefClientInstaller.Unit.Tests.ps1
	modules/InstallationSDK.ChefClientInstaller/InstallationSDK.ChefClientInstaller.psproj
This commit is contained in:
mattsch 2015-03-20 18:17:03 -07:00
Родитель cea6205cd9 84ba75a063
Коммит d64bcb2901
17 изменённых файлов: 920 добавлений и 72 удалений

4
core/Main.ps1 Normal file
Просмотреть файл

@ -0,0 +1,4 @@
$ErrorActionPreference = "Stop"
# Insert your custom logic below...

8
core/Start.cmd Normal file
Просмотреть файл

@ -0,0 +1,8 @@
ECHO Starting custom deployment script
REM Switch CWD to same directory as batch file
cd /d %~dp0
PowerShell.exe -ExecutionPolicy Unrestricted .\Start.ps1 >> ".\Start.log" 2>&1
EXIT /B %ERRORLEVEL%

32
core/Start.ps1 Normal file
Просмотреть файл

@ -0,0 +1,32 @@
$VerbosePreference = "Continue"
$exitCode = 1
& {
$VerbosePreference = "Continue"
Write-Verbose "Starting installation from $(pwd)"
try
{
.\Main.ps1
}
catch
{
Write-Verbose "$_"
if ($_ -is [System.Management.Automation.ErrorRecord])
{
Write-Verbose "$($_.ScriptStackTrace)"
}
elseif ($_ -is [Exception])
{
Write-Verbose "$($_.ErrorRecord.ScriptStackTrace)"
}
throw
}
$Script:exitCode = 0
} *> .\Start.transcript.log
exit $exitCode

Просмотреть файл

@ -0,0 +1,159 @@
$ErrorActionPreference = "Stop"
[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime")
$roleEnvironment = [Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]
function Get-CloudServiceRole
{
<#
.SYNOPSIS
Gets one or more Azure Roles.
.PARAMETER Current
If set, gets only the current role.
#>
[CmdletBinding()]
Param
(
[Switch] $Current
)
Process
{
if ($Current)
{
return $roleEnvironment::CurrentRoleInstance.Role
}
else
{
return ($roleEnvironment::Roles).Value
}
}
}
function Get-CloudServiceConfigurationSettingValue
{
<#
.SYNOPSIS
Gets the configuration setting value from the Azure CSCFG.
.PARAMETER Name
The setting to retrieve.
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[string] $Name
)
Process
{
try
{
return $roleEnvironment::GetConfigurationSettingValue($Name);
}
catch
{
# TODO: DO we want this in prod? Or just return an error?
# If setting doesn't exist. Just return null.
return $null;
}
}
}
function Get-CloudServiceRoleInstance
{
<#
.SYNOPSIS
Gets one or more Azure Role Instances.
.PARAMETER Role
The role to get the instances of.
.PARAMETER Current
If set, gets the current role instance.
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory, ParameterSetName="ByRole")]
[ValidateNotNull()]
[Object] $Role,
[Parameter(ParameterSetName="ByCurrent")]
[Switch] $Current
)
Process
{
if ($PSCmdlet.ParamSetName -eq "ByRole")
{
return $Role.Instances
}
else
{
return $roleEnvironment::CurrentRoleInstance
}
}
}
function Get-CloudServiceLocalResource
{
<#
.SYNOPSIS
Gets details for an Azure Local Resource.
.PARAMETER ResourceName
The name of the Azure Local Resource.
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[String] $ResourceName
)
Process
{
try
{
return $roleEnvironment::GetLocalResource($resourceName)
}
catch
{
Write-Warning "Unable to get to get Azure local resource $ResourceName, possibly because it does not exist ($_)."
return $null
}
}
}
function Get-CloudServiceEnvironmentType
{
<#
.SYNOPSIS
Gets the type of environment that the scripts are currently running in.
.NOTES
Will be one of "Emulated", "Azure", or "NotAvailable"
#>
[CmdletBinding()]
Param()
Process
{
if ($roleEnvironment::IsAvailable)
{
if ($roleEnvironment::IsEmulated)
{
return "Emulated"
}
return "Azure"
}
return "NotAvailable"
}
}
Export-ModuleMember Get-CloudServiceRole
Export-ModuleMember Get-CloudServiceRoleInstance
Export-ModuleMember Get-CloudServiceLocalResource
Export-ModuleMember Get-CloudServiceEnvironmentType
Export-ModuleMember Get-CloudServiceConfigurationSettingValue

Просмотреть файл

@ -0,0 +1,18 @@
{
"Name": "InstallationSDK.AzureServiceRuntime",
"OutputPath": "bin",
"Module": {
"Id": "6D6BB33F-84DE-408D-869E-747082A76505",
"Name": "InstallationSDK.AzureServiceRuntime",
"Version": "1.0.0",
"Author": "ampdmi",
"CompanyName": "Microsoft",
"Description": "PowerShell Cmdlets for interacting with the Azure Service Runtime.",
"Copyright": "(c) 2014 Microsoft. All rights reserved.",
"PowerShellVersion": "3.0",
"RequiredModules": []
},
"Files": {
"RootModule": "InstallationSDK.AzureServiceRuntime.psm1"
}
}

Просмотреть файл

@ -31,7 +31,7 @@ $ErrorActionPreference = "STOP"
$modulesToCleanup = @($ModuleUnderTest)
Import-Module Pester
Import-Module Pester
#region Helper Functions
@ -71,6 +71,60 @@ function Cleanup
Remove-Variable -Name TestDrive -Force -Scope Script -ErrorAction SilentlyContinue
}
function CreateRoleInstanceEndpoint
{
param(
[string]$parsableIPAddress,
[string]$parsableIPAddressPublic,
[string]$protocol
)
$ip = $parsableIPAddress.Split(":")
$ipEndpoint = New-Object System.Net.IPEndPoint -ArgumentList ([System.Net.IPAddress]$ip[0], $ip[1])
$publicIpEndpoint = $null
if($parsableIPAddressPublic)
{
$public = $parsableIPAddressPublic.Split(":")
$publicIpEndpoint = New-Object System.Net.IPEndPoint -ArgumentList ([System.Net.IPAddress]$public[0], $public[1])
}
New-Object psobject -Property @{
"IPEndpoint" = $ipEndpoint
"PublicIPEndpoint" = $publicIpEndpoint
"Protocol" = $protocol
}
}
function CreateVirtualIPEndpoint
{
param(
[string]$groupName,
[System.Net.IPAddress[]]$ipAddresses,
$roleInstanceEndpoints
)
# Weird dictionary thingy
$instanceEndpoints = @{}
$i = 0
foreach($roleInstanceEndpoint in $roleInstanceEndpoints)
{
$instanceEndpoints[($i++)] = $roleInstanceEndpoint
}
$virtualIpEndpoints = @{}
foreach($ipAddress in $ipAddresses)
{
$virtualIpEndpoints[$ipAddress] = New-Object psobject -Property @{
"PublicIPAddress" = $ipAddress
"InstanceEndpoints" = $instanceEndpoints
}
}
New-Object psobject -Property @{
"VirtualIPGroupName" = $groupName
"VirtualIPEndpoints" = $virtualIpEndpoints
}
}
#endregion
#region Setup
@ -87,9 +141,15 @@ Cleanup
WriteVerbose "Building Project Dependencies"
foreach ($dependantPSproject in $PSProjDepedencies)
{
New-ProjectBuild -Path $dependantPSproject
$moduleName = [IO.Path]::GetFileNameWithoutExtension((Split-Path -Leaf $dependantPSproject))
$moduleFolder = Join-Path (Split-Path $dependantPSproject) "bin"
$moduleName = [IO.Path]::GetFileNameWithoutExtension((Split-Path -Leaf $dependantPSproject))
if (Test-Path $moduleFolder)
{
Remove-Item -Path $moduleFolder -Recurse -Force
}
New-ProjectBuild -Path $dependantPSproject
SafeAddModuleFolderToPSRoot $moduleFolder
ImportModuleAndToCleanupList $moduleName
@ -98,7 +158,13 @@ foreach ($dependantPSproject in $PSProjDepedencies)
#
# Build Module Under Test
#
$ModuleRoot = Join-Path $PSScriptRoot "bin"
WriteVerbose "Building the Module Under Test"
if (Test-Path $ModuleRoot)
{
Remove-Item -Path $ModuleRoot -Recurse -Force
}
$psprojFile = Resolve-Path (Join-Path $PSScriptRoot "*.psproj")
Write-Verbose "Building psproj: $psprojFile"
New-ProjectBuild -Path $psprojFile
@ -107,7 +173,6 @@ New-ProjectBuild -Path $psprojFile
# Loading Module-Under-Test
#
WriteVerbose "Loading Module-Under-Test"
$ModuleRoot = Join-Path $PSScriptRoot "bin"
SafeAddModuleFolderToPSRoot $moduleRoot
Import-Module $ModuleUnderTest
@ -272,6 +337,63 @@ InModuleScope $ModuleUnderTest {
$path | Should Match ([Regex]::Escape("$InstallLocation\chef\bin"))
}
}
Describe "Export-ChefAzureOhaiHints" {
Mock Get-CloudServiceRoleInstance {
param([Switch]$Current)
# These RoleInstanceEndpoints were taken directly from a "test" azure instance
$endpoint1 = CreateRoleInstanceEndpoint "100.68.46.96:80" "255.255.255.255:80" "http"
$rdp = CreateRoleInstanceEndpoint "100.68.46.96:3389" $null "tcp"
$rdpInput = CreateRoleInstanceEndpoint "100.68.46.96:20000" "255.255.255.255:3389" "tcp"
New-Object psobject -Property @{
"DeploymentID" = "9662c0f4355042c7b2eae7dd06e70c28"
"ID" = "WebRole1_IN_0"
"UpdateDomain" = 0
"FaultDomain" = 0
"Role" = New-Object psobject -property @{"Name" = "WebRole1"}
"InstanceEndpoints" = @{
"Endpoint1" = $endpoint1
"Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" = $rdp
"Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" = $rdpInput
}
# Couldn't get a good working sample of VirtualIPGroups, these VirtualIPEndpoints were filled in using reasonable, expected values
"VirtualIPGroups" = @{
"Group1" = CreateVirtualIPEndpoint "SomeGroup1" ("192.167.0.1","192.167.0.2","127.0.0.1") ($endpoint1,$rdp,$rdpInput)
"Group2" = CreateVirtualIPEndpoint "SomeGroup2" ("255.255.255.255","0.0.0.0","169.245.214.223") ($endpoint1,$rdp)
}
}
}
$roleInstance = Get-CloudServiceRoleInstance -Current
$hintsDirectory = "TestDrive:\\Chef\Ohai\Hints"
Export-ChefAzureOhaiHints -path $hintsDirectory
$expectedAzureHintFile = Join-Path $hintsDirectory "azure.json"
It "Should Parse Correctly" {
$expectedAzureHintFile | Should Exist
{ ConvertFrom-Json ((Get-Content $expectedAzureHintFile) -join "`n") } | Should Not Throw
}
It "Should have every property defined" {
$deserialized = ConvertFrom-Json ((Get-Content $expectedAzureHintFile) -join "`n")
$deserialized.deployment_id | Should Not BeNullOrEmpty
$deserialized.deployment_id | Should BeExactly $roleInstance.DeploymentID
$deserialized.role | Should Not BeNullOrEmpty
$deserialized.role | Should BeExactly $roleInstance.Role.Name
$deserialized.instance_endpoints.'Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp'.ip_endpoint | Should Not BeNullOrEmpty
$deserialized.instance_endpoints.'Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp'.ip_endpoint | Should BeExactly $roleInstance.InstanceEndpoints["Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp"].IPEndpoint.ToString()
$deserialized.instance_endpoints.'Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp'.public_ip_endpoint | Should BeNullOrEmpty
$deserialized.virtual_ip_groups.'Group1'.group_name | Should Not BeNullOrEmpty
$deserialized.virtual_ip_groups.'Group1'.group_name | Should BeExactly $roleInstance.VirtualIPGroups["Group1"].VirtualIPGroupName
$deserialized.virtual_ip_groups.'Group1'.virtual_ip_endpoints.'192.167.0.1'.instance_endpoints.'0'.ip_endpoint | Should Not BeNullOrEmpty
$deserialized.virtual_ip_groups.'Group1'.virtual_ip_endpoints.'192.167.0.1'.instance_endpoints.'0'.ip_endpoint | Should BeExactly $roleInstance.VirtualIPGroups["Group1"].VirtualIPEndpoints[[Net.IPAddress]"192.167.0.1"].InstanceEndpoints[0].IPEndpoint.ToString()
}
}
}
#

Просмотреть файл

@ -98,7 +98,7 @@ function Get-ChefClientConfig
{
# Regex matches simple "somekey value" pattern
# Parses out custom Ruby (like now = Time.new)
Get-Content -Path $Path | foreach {
Get-Content -Path $Path | where { $_ } | foreach {
if (-not ($_ -match "^[a-zA-Z0-9_]*\s*[:'`"0-9].*['`"]?" ))
{
# Ran into this scenario before
@ -223,7 +223,7 @@ function Save-ChefClientConfig
function Get-ChefNodeList
{
<#
<#
.SYNOPSIS
Gets the list of nodes from the chef server
@ -277,6 +277,106 @@ function Get-ChefNodeList
}
}
function Export-ChefAzureOhaiHints
{
<#
.SYNOPSIS
Generate Azure Ohai Hints File
#>
[CmdletBinding()]
param(
[ValidateNotNullOrEmpty()]
[string]$Path = 'c:/chef/ohai/hints'
)
# There may be some issues with calling Get-CloudServiceRoleInstance, so call it twice to make sure we get the proper object
Get-CloudServiceRoleInstance -Current -ErrorAction SilentlyContinue | out-Null
$roleInstance = Get-CloudServiceRoleInstance -Current
# Because Ohai::Config[:hints_path] is actual ruby code, not a configuration settting, we can't use the ClientRBObject.
# So just hard code the path here. Alternatively, we could try to read/parse from the client.rb file directly, but that's costly.
# And since it's ruby code, it might be a non-string (path_to_hints.to_s or something weird like that) that PSH wouldn't know what to do with.
# This seemed the least hacky way...
# TL;DR - Important! If the path to \Ohai\Hints ($Path) is changed, also change it in Client.Rb directly!
# Azure Ohai Plugin specifically looks for an "azure" hint file (which would be named azure.json)
New-Item $Path -Force -ItemType Directory -ErrorAction SilentlyContinue
$azureJsonFile = Join-Path $Path "azure.json"
New-Item -Type Directory -Force -Path $Path -ErrorAction SilentlyContinue | Out-Null
$azureHints = @{}
$azureHints["deployment_id"] = $roleInstance.DeploymentID
$azureHints["instance_id"] = $roleInstance.Id
$azureHints["update_domain"] = $roleInstance.UpdateDomain
$azureHints["fault_domain"] = $roleInstance.FaultDomain
$azureHints["role"] = $roleInstance.Role.Name
$azureHints["instance_endpoints"] = @{}
foreach($endpointKey in $roleInstance.InstanceEndpoints.Keys) {
$endpoint = $roleInstance.InstanceEndpoints[$endpointKey]
$azureHints["instance_endpoints"][$endpointKey] = @{
"ip_endpoint" = Convert-ToStringSafe $endpoint.IPEndpoint
"public_ip_endpoint" = Convert-ToStringSafe $endpoint.PublicIPEndpoint
"protocol" = $endpoint.Protocol
}
}
$azureHints["virtual_ip_groups"] = @{}
foreach($ipGroupKey in $roleInstance.VirtualIPGroups.Keys) {
$group = $roleInstance.VirtualIPGroups[$ipGroupKey]
$vip_ip_endpoints = @{}
foreach($vipEndpointKey in $group.VirtualIPEndpoints.Keys) {
$vipEndpoint = $group.VirtualIPEndpoints[$vipEndpointKey]
$vip_instance_endpoints = @{}
foreach($instanceEndpointKey in $vipEndpoint.InstanceEndpoints.Keys) {
$instanceEndpoint = $vipEndpoint.InstanceEndpoints[$instanceEndpointKey]
$vip_instance_endpoints[(Convert-ToStringSafe $instanceEndpointKey)] = @{
"ip_endpoint" = Convert-ToStringSafe $instanceEndpoint.IPEndpoint
"public_ip_endpoint" = Convert-ToStringSafe $instanceEndpoint.PublicIPEndpoint
"protocol" = $instanceEndpoint.Protocol
}
}
$vip_ip_endpoints[(Convert-ToStringSafe $vipEndpointKey)] = @{
"public_ip_address" = Convert-ToStringSafe $vipEndpoint.PublicIPAddress
"instance_endpoints" = $vip_instance_endpoints
}
}
$azureHints["virtual_ip_groups"][$ipGroupKey] = @{
"group_name" = $group.VirtualIPGroupName
"virtual_ip_endpoints" = $vip_ip_endpoints
}
}
# In my testing, it can go as deep as depth "6". Any less than that and the cmdlet will just call .ToString() on nested collections
$azureHints | ConvertTo-Json -Depth 6 | Set-Content $azureJsonFile
}
function Convert-ToStringSafe
{
<#
.SYPNOSIS
Quick utility function to prevent things from getting too deserialized (e.g. IPAddress getting each of its properties deserialized)
#>
param(
$value
)
if($value -or ($value -eq 0))
{
$value.ToString()
}
else
{
""
}
}
# Wrap cmd exe files to make things mockable. Can't use Start-Process because I want the output (Start-Process would write to a file first with -RedirectStandardOutput)
function Invoke-Knife
{
@ -316,4 +416,5 @@ param(
Export-ModuleMember Install-ChefClient
Export-ModuleMember Get-ChefClientConfig
Export-ModuleMember Save-ChefClientConfig
Export-ModuleMember Get-ChefNodeList
Export-ModuleMember Get-ChefNodeList
Export-ModuleMember Export-ChefAzureOhaiHints

Просмотреть файл

@ -25,43 +25,21 @@ namespace Microsoft.OnlinePublishing.Chef
/// <summary>
/// Stop the Chef Client windows service with the default wait time of 1 minute.
/// </summary>
public static void Stop()
/// <param name="terminateOnFailure">Should the process terminate the processes of the Client service if it failes to stop in alloted time.</param>
public static void Stop(bool terminateOnFailure)
{
Stop(new TimeSpan(0, 1, 0));
Stop(new TimeSpan(0, 1, 0), terminateOnFailure);
}
/// <summary>
/// Stop the Chef Client windows service.
/// </summary>
/// <param name="timeToWait">Wait time for operation to complete.</param>
public static void Stop(TimeSpan timeToWait)
/// <param name="terminateOnFailure">Should the process terminate the processes of the Client service if it failes to stop in alloted time.</param>
public static void Stop(TimeSpan timeToWait, bool terminateOnFailure)
{
try
{
// Stop Chef Client
Trace.TraceInformation("Chef Client - attempting to stop the Chef Client windows service.");
using (var chefService = new ServiceController("chef-client"))
{
if (chefService != null && chefService.Status != ServiceControllerStatus.Stopped)
{
chefService.Stop();
chefService.WaitForStatus(ServiceControllerStatus.Stopped, timeToWait);
Trace.TraceInformation("Chef Client - Chef Client windows service Stopped.");
}
else
{
Trace.TraceInformation("Chef Client - Chef Client windows service is not running.");
}
}
}
catch (System.ServiceProcess.TimeoutException)
{
Trace.TraceInformation("Chef Client - failed to stop Chef Client in time allotted [{0}].", timeToWait);
}
catch (InvalidOperationException e)
{
Trace.TraceInformation("Chef Client - Invalid Operation, is the role running with elevated privileges. Ex:{0}.", e.ToString());
}
var service = new WindowsService() { Name = "chef-client", TerminateOnFailure = terminateOnFailure, TimeToWait = timeToWait };
service.Stop();
}
/// <summary>
@ -69,37 +47,21 @@ namespace Microsoft.OnlinePublishing.Chef
/// </summary>
public static void Start()
{
try
{
RoleEnvironment.Changing += ChefConfigChanging;
RoleEnvironment.StatusCheck += Chef_StatusCheck;
Start(new TimeSpan( 0, 1, 0) );
}
// Start Chef Client - wait 30 seconds
Trace.TraceInformation("Chef Client - Attempting to start Chef-Client.");
using (var chefService = new ServiceController("chef-client"))
{
if (chefService != null && chefService.Status != ServiceControllerStatus.Running)
{
chefService.Start();
chefService.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 30));
Trace.TraceInformation("Chef Client - Chef-Client Started.");
}
else
{
Trace.TraceInformation("Chef Client - Chef-Client previously running.");
}
}
/// <summary>
/// Start Chef Client windows service.
/// </summary>
public static void Start(TimeSpan timeToWait)
{
RoleEnvironment.Changing += ChefConfigChanging;
RoleEnvironment.StatusCheck += Chef_StatusCheck;
ClientService.statusCheckFilePath = CloudConfigurationManager.GetSetting("ChefClient_SetBusyCheck");
}
catch (System.ServiceProcess.TimeoutException)
{
Trace.TraceInformation("Chef Client - failed to start Chef Client within time range.");
}
catch (InvalidOperationException e)
{
Trace.TraceInformation("Chef Client - Invalid Operation, is the role running with elevated privileges. Ex:{0}.", e.ToString());
}
// Start Chef Client - wait 30 seconds
var service = new WindowsService() { Name = "chef-client", TimeToWait = timeToWait };
service.Start();
ClientService.statusCheckFilePath = CloudConfigurationManager.GetSetting("ChefClient_SetBusyCheck");
}
/// <summary>
@ -114,6 +76,7 @@ namespace Microsoft.OnlinePublishing.Chef
{
return;
}
e.SetBusy();
}
@ -139,7 +102,7 @@ namespace Microsoft.OnlinePublishing.Chef
c.ConfigurationSettingName == "ChefClient_Role" ||
c.ConfigurationSettingName == "ChefClient_Environment" ))
{
Stop(new TimeSpan(0, 0, 5));
Stop(new TimeSpan(0, 0, 30), true);
e.Cancel = true;
}
}

Просмотреть файл

@ -0,0 +1,174 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WindowsService.cs" company="Microsoft Corporation">
// Copyright (C) Microsoft. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.OnlinePublishing.Chef
{
using System;
using System.Diagnostics;
using System.ServiceProcess;
using System.Management;
/// <summary>
/// Windows Service gives the ability to stop a windows service (namely chef-client).
/// If the service failes to stop in a given timespan, then the service and its child processes are terminated.
/// </summary>
class WindowsService
{
/// <summary>
/// Gets or sets the Name of the Windows Service.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the alloted time to wait during shutdown sequence.
/// </summary>
public TimeSpan TimeToWait { get; set; }
/// <summary>
/// Gets or sets whether or not to terminate processes upon failure to stop teh service in the alloted time to wait.
/// </summary>
public bool TerminateOnFailure { get; set; }
/// <summary>
/// Start Chef Client windows service.
/// </summary>
public void Start()
{
try
{
// Start Chef Client - wait 30 seconds
Trace.TraceInformation("{0} - Attempting to start {0}.", Name);
using (var chefService = new ServiceController(Name))
{
if (chefService != null && chefService.Status != ServiceControllerStatus.Running)
{
chefService.Start();
chefService.WaitForStatus(ServiceControllerStatus.Running, TimeToWait);
Trace.TraceInformation("{0} - {0} Started.", Name);
}
else
{
Trace.TraceInformation("{0} - {0} previously running.", Name);
}
}
}
catch (System.ServiceProcess.TimeoutException)
{
Trace.TraceInformation("{0} - failed to start Chef Client within time range.", Name);
}
catch (InvalidOperationException e)
{
Trace.TraceInformation("{0} - Invalid Operation, is the role running with elevated privileges. Ex:{1}.", Name, e.ToString());
}
}
/// <summary>
/// Stop the Windows Service, if Terminate on Failure, then terminate process and all child spawn.
/// </summary>
public void Stop()
{
try
{
// Stop Chef Client
Trace.TraceInformation("{0} - attempting to stop the {0} windows service.", Name);
using (var chefService = new ServiceController(Name))
{
if (chefService != null && chefService.Status != ServiceControllerStatus.Stopped)
{
chefService.Stop();
chefService.WaitForStatus(ServiceControllerStatus.Stopped, TimeToWait);
Trace.TraceInformation("{0} - {0} windows service Stopped.", Name);
}
else
{
Trace.TraceInformation("{0} - {0} windows service is not running.", Name);
}
}
}
catch (System.ServiceProcess.TimeoutException)
{
Trace.TraceInformation("{0} - failed to stop {0} in time allotted [{1}].", Name, TimeToWait);
if (TerminateOnFailure)
{
Trace.TraceInformation("{0} - attempting to terminate service process and its children.", Name);
KillService();
}
}
catch (InvalidOperationException e)
{
Trace.TraceInformation("{0} - Invalid Operation, is the role running with elevated privileges. Ex:{1}.", Name, e.ToString());
}
}
/// <summary>
/// Locate process by service name and kill it and its spawn.
/// </summary>
private void KillService()
{
var searcher = new ManagementObjectSearcher(
"SELECT * " +
"FROM Win32_Service " +
"WHERE Name=\"" + Name + "\"");
var collection = searcher.Get();
foreach (var item in collection)
{
var serviceProcessId = (UInt32)item["ProcessId"];
KillSpawnedProcesses(serviceProcessId);
Trace.TraceInformation("{0} - Killing Service process with Id [{1}].", Name, serviceProcessId);
KillProcess(serviceProcessId);
}
}
/// <summary>
/// Kill process and all spawn. Travers all children for multi generation children.
/// </summary>
/// <param name="parentProcessId">Parent ID</param>
private void KillSpawnedProcesses(UInt32 parentProcessId)
{
Trace.TraceInformation("{0} - Finding processes spawned by process with Id [" + parentProcessId + "]", Name);
var searcher = new ManagementObjectSearcher(
"SELECT * " +
"FROM Win32_Process " +
"WHERE ParentProcessId=" + parentProcessId);
var collection = searcher.Get();
if (collection.Count > 0)
{
Trace.TraceInformation("{0} - Killing [{1}] processes spawned by process with Id [{2}].", Name, collection.Count, parentProcessId);
foreach (var item in collection)
{
var childProcessId = (UInt32)item["ProcessId"];
KillSpawnedProcesses(childProcessId);
KillProcess(childProcessId);
}
}
}
/// <summary>
/// Attempt to kill process. It can ocurr that the process has been killed prior to getting to this statement as the process tree is being killed.
/// </summary>
/// <param name="processId">ID of process to kill.</param>
private void KillProcess(uint processId)
{
try
{
if ((int)processId != Process.GetCurrentProcess().Id)
{
var process = Process.GetProcessById((int)processId);
Trace.TraceInformation("{0} - Killing process [{1}] with Id [{2}]", Name, process.ProcessName, processId);
process.Kill();
}
}
catch (ArgumentException e)
{
Trace.TraceInformation("{0} - failed to find process [{1}]. Process already killed. Ex:{2}", Name, processId, e.ToString());
}
catch (System.ComponentModel.Win32Exception e)
{
Trace.TraceInformation("{0} - failed to kill process [{1}]. Ex:{2}", Name, processId, e.ToString());
}
}
}
}

Просмотреть файл

@ -64,6 +64,14 @@ if ($config -and $config.name)
$name = $config.name
}
# Allow to pull from CSCFG allowing for easier targetting of node name prefix, this can only be set on initial deployment
# once the node name is established, the exisitng name will always win.
$cscfgName = Get-CloudServiceConfigurationSettingValue "ChefClient_Name"
if ($cscfgName)
{
$name = $cscfgName
}
# Add Instance number to the node name
Write-Output "Role Instance Name: $($roleInstance.Id)"
$nodeName = $($roleInstance.Id).Replace($roleName, $name)
@ -239,6 +247,11 @@ else
# Value from Cloud Service CsCfg always wins.
$chefRole = Get-CloudServiceConfigurationSettingValue "ChefClient_Role"
$chefEnvironment = Get-CloudServiceConfigurationSettingValue "ChefClient_Environment"
$azureRegion = Get-CloudServiceConfigurationSettingValue "ChefClient_Region"
$servicefunction = Get-CloudServiceConfigurationSettingValue "ChefClient_Function"
$serviceName = Get-CloudServiceConfigurationSettingValue "ChefClient_ServiceName"
# Create first-run-bootstrap.json to register new node with Chef Server
if ($chefRole -or ($config -and $config.role))
{
@ -248,7 +261,19 @@ if ($chefRole -or ($config -and $config.role))
$chefRole = $($config.role)
}
$bootStrapperFile = "first-run-bootstrap.json"
$bootStrapper = "{`r`n `"run_list`": [ `"role[$chefRole]`" ]`r`n}"
$bootStrapper =
@"
{
"run_list": [ "role[$chefRole]" ],
"azure": {
"chef_environment":"$chefEnvironment",
"chef_role": "$chefRole",
"region": "$azureRegion",
"function": "$servicefunction",
"service_name": "$serviceName"
}
}
"@
Write-Output "Setting bootstrap content: $bootStrapper"
$pathToBootStrapper = Join-Path $RootPath $bootStrapperFile
@ -268,6 +293,9 @@ if ($chefEnvironment)
Copy-Item -Path $TemplateClientRb -Destination $pathToClientRb -Force
$ClientRbObject | Save-ChefClientConfig -Path $pathToClientRb -Append
# Setup Azure Ohai
Export-ChefAzureOhaiHints -Path "$RootPath/ohai/hints"
start-service chef-client
Write-Output "Script:main.ps1 exiting"

Просмотреть файл

@ -0,0 +1,5 @@
log_level :info
log_location 'c:/chef/client_' + Time.new.strftime("%Y%m%d") + '.log'
cache_path 'c:\chef'
client_key 'c:\chef\client.pem'
Ohai::Config[:hints_path] = [ 'c:/chef/ohai/hints' ]

155
nuget/Install.ps1 Normal file
Просмотреть файл

@ -0,0 +1,155 @@
Param
(
# Path to the folder where the package is installed
$installPath,
# Path to the tools directory in the folder where the package is installed
$toolsPath,
# A reference to the package object
$package,
# A reference to the EnvDTE project object and represents the project the package is installed into.
# http://msdn.microsoft.com/en-us/library/51h9a6ew(v=VS.80).aspx
$project
)
# http://msdn.microsoft.com/en-us/library/aa983962(VS.71).aspx
$BuildTypes = @{
"None" = "0";
"Compile" = "1";
"Content" = "2";
}
$CopyToOutputDirectory = @{
"DoNotCopy" = "0";
"CopyAlways" = "1";
"CopyIfNewer" = "2";
}
function Add-ContentsToProjectOutput
{
<#
.DESCRIPTION
If the project item given by ProjectItem exists at the corresponding location given by PackagePath,
then sets its properties such that the item will be copied to the build output. The intent is to not
modify user's file which may also be present under the given project item.
.PARAMETER ProjectItem
The project item; should be a child object of a VS project.
.PARAMETER PackagePath
The physical path that should correspond to ProjectItem. If the path does not exist or otherwise does
not match ProjectItem, then it will not be modified.
.PARAMETER ProjectPath
The logical path of the ProjectItem within the project. Used purely for logging.
.PARAMETER Recurse
A value indicating whether a ProjectItem whose type is PhysicalDirectory should be traversed.
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory)]
[ValidateNotNull()]
[Object] $ProjectItem,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[String] $PackagePath,
[Parameter()]
[ValidateNotNullOrEmpty()]
[String] $ProjectPath = "./",
[Parameter()]
[Switch] $Recurse
)
Process
{
$name = $ProjectItem.Name
if ((IsPhysicalFolder $ProjectItem) -and $Recurse)
{
if (-not (Test-Path $PackagePath -PathType Container))
{
Write-Verbose "Folder '$ProjectPath' does not exist at location '$PackagePath'"
return $null
}
Write-Verbose "'$ProjectPath' maps to folder '$PackagePath', checking its contents"
return ($ProjectItem.ProjectItems | % {
Add-ContentsToProjectOutput `
-ProjectItem $_ `
-PackagePath (Join-Path $PackagePath $_.Name) `
-ProjectPath (Join-Path $ProjectPath $_.Name) `
-Recurse:$Recurse
})
}
elseif (IsPhysicalFile $ProjectItem)
{
if (-not (Test-Path $PackagePath -PathType Leaf))
{
Write-Verbose "File '$ProjectPath' does not exist at location '$PackagePath'"
return $null
}
Write-Verbose "'$ProjectPath' maps to file '$PackagePath', adding it to build output"
$ProjectItem.Properties.Item("CopyToOutputDirectory").Value = $CopyToOutputDirectory.CopyIfNewer
$ProjectItem.Properties.Item("BuildAction").Value = $BuildTypes.None
return $ProjectItem
}
}
}
function IsPhysicalFolder
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory)]
[ValidateNotNull()]
[Object] $ProjectItem
)
Process
{
return ($ProjectItem.Kind -ieq "{6bb5f8ef-4483-11d3-8bcf-00c04f8ec28c}")
}
}
function IsPhysicalFile
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory)]
[ValidateNotNull()]
[Object] $ProjectItem
)
Process
{
return ($ProjectItem.Kind -ieq "{6bb5f8ee-4483-11d3-8bcf-00c04f8ec28c}")
}
}
$rootItem = $project.ProjectItems.Item("Deployment")
if (-not $rootItem)
{
Write-Warning "Root 'Deployment' item does not exist."
return
}
$modified = Add-ContentsToProjectOutput `
-ProjectItem $rootItem `
-PackagePath (Join-Path $installPath "content/Deployment/")`
-ProjectPath $rootItem.Name `
-Recurse
Write-Verbose "Modified $($modified.Count) item(s)."
return $modified

Просмотреть файл

@ -0,0 +1,51 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>Microsoft.OnlinePublishing.InstallationSDK.ChefClientInstaller</id>
<version>0.1.7</version>
<title>Microsoft Online Publishing Installation SDK Chef Client Installer</title>
<authors>ampdmi@microsoft.com</authors>
<owners>ampdmi@microsoft.com</owners>
<projectUrl>http://aka.ms/msompinstallationsdk</projectUrl>
<description>PowerShell scripts for performing installation of Chef Client for Azure Roles.</description>
<tags>Online-Media-Publishing MS-Internal-Only Azure Installation Deployment SDK</tags>
<releaseNotes>
0.1.7:
- Terminate the Chef-Client processes if the service fails to stop in the allotted time.
0.1.6:
- Write Azure Role Environment information into an Ohai hints file, where the built in Azure Ohai plug-in can read from.
0.1.5:
- Added Role and Environment to be cscfg configurable. ChefClient_ServerUrl, ChefClient_Role,
and ChefClient_Environment are now configurable from loud service config updates.
- Add a set instance busy check file and configurable path in ChefClient_SetBusyCheck
0.1.4:
- Added support for deploying the node-based encrypted_data_bag_secret.
0.1.3:
- Removed Client MSI from package so it meets size requirements for MS-NuGet and NuGet.
0.1.2:
- Update to client to create base folder structure and PSModulePath.
0.1.1:
- BugFix:Upon OS update, client service is not reinstalled.
0.1.0:
- Initial Release
See http://aka.ms/msompinstallationsdk for complete release history.
Join the ampdmitalk DG to ask questions and take part in the deployment and monitoring community.
</releaseNotes>
<dependencies>
<dependency id="Microsoft.OnlinePublishing.InstallationSDK" version="1.1.0"/>
</dependencies>
<frameworkAssemblies>
<frameworkAssembly assemblyName="System.ServiceProcess"/>
<frameworkAssembly assemblyName="System.Management"/>
</frameworkAssemblies>
</metadata>
<files>
<file src="..\Nuget\Install.ps1" target="tools" />
<!-- InstallationSDK.ChefClientInstaller Module -->
<file src="..\Modules\InstallationSDK.ChefClientInstaller\bin\InstallationSDK.ChefClientInstaller\InstallationSDK.ChefClientInstaller.psm1" target="content\Deployment\Modules\InstallationSDK.ChefClientInstaller" />
<file src="..\Modules\InstallationSDK.ChefClientInstaller\bin\InstallationSDK.ChefClientInstaller\InstallationSDK.ChefClientInstaller.psd1" target="content\Deployment\Modules\InstallationSDK.ChefClientInstaller" />
<file src="..\Modules\InstallationSDK.ChefClientInstaller\bin\InstallationSDK.ChefClientInstaller\resources\Readme.txt" target="content\Deployment\Modules\InstallationSDK.ChefClientInstaller\resources" />
<file src="..\Modules\InstallationSDK.ChefClientInstaller\bin\InstallationSDK.ChefClientInstaller\code\*.*" target="content" />
<file src="..\Modules\InstallationSDK.ChefClientInstaller\bin\InstallationSDK.ChefClientInstaller\script\*.*" target="content\Deployment" />
</files>
</package>

Просмотреть файл

@ -0,0 +1,33 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>Microsoft.OnlinePublishing.InstallationSDK</id>
<version>1.1.0</version>
<title>Microsoft.OnlinePublishing.InstallationSDK</title>
<authors>ampdmi@microsoft.com</authors>
<owners>ampdmi@microsoft.com</owners>
<projectUrl>http://aka.ms/msompinstallationsdk</projectUrl>
<description>PowerShell scripts for performing service installation tasks in an Azure Cloud Service.</description>
<tags>Online-Media-Publishing MS-Internal-Only Azure Installation Deployment SDK</tags>
<releaseNotes>
1.1.0:
- Added the Get-CloudServiceConfigurationSettingValue for getting the value from the cscfg while running in Azure.
1.0.0:
- Initial Release
See http://aka.ms/msompinstallationsdk for complete release history.
Join the ampdmitalk DG to ask questions and take part in the deployment and monitoring community.
</releaseNotes>
</metadata>
<files>
<file src="..\Nuget\Install.ps1" target="tools" />
<file src="..\Core\Main.ps1" target="content\Deployment\" />
<file src="..\Core\Start.ps1" target="content\Deployment\" />
<file src="..\Core\Start.cmd" target="content\Deployment\" />
<!-- InstallationSDK.AzureServiceRuntime Module -->
<file src="..\Modules\InstallationSDK.AzureServiceRuntime\bin\InstallationSDK.AzureServiceRuntime\InstallationSDK.AzureServiceRuntime.psm1" target="content\Deployment\Modules\InstallationSDK.AzureServiceRuntime" />
<file src="..\Modules\InstallationSDK.AzureServiceRuntime\bin\InstallationSDK.AzureServiceRuntime\InstallationSDK.AzureServiceRuntime.psd1" target="content\Deployment\Modules\InstallationSDK.AzureServiceRuntime" />
</files>
</package>

Просмотреть файл

@ -1,5 +0,0 @@
now = Time.new
log_level :info
log_location 'c:/chef/client_' + now.strftime("%Y%m%d") + '.log'
cache_path 'c:\chef'
client_key 'c:\chef\client.pem'