Chef Client NuGet package for Chef Server 12
- Configure using the config.json file - Cleanup Main.ps1 script to get it to work out of box - Add ticks to the node name to avoid name collisions when Azure flattens/recreates a node - Move the last of the stuff over to C:\ git-tfs-id: [http://vstfmsn:8080/tfs/MSN01]$/PubServices/Deployment/InstallationSDK/Main/src/Modules/InstallationSDK.ChefClientInstaller;C1143058
This commit is contained in:
Родитель
566e44b8ed
Коммит
86146cc6b3
|
@ -0,0 +1,283 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Runs the unit tests for the Chef Client Installer
|
||||
|
||||
.DESCRIPTION
|
||||
Builds the Chef Client Installer using the Powershellution Module. Then
|
||||
executes unit tests for the Chef Client Installer. Functions are
|
||||
mocked to have no impact on the computer on which you execute the script
|
||||
and to have no external dependencies.
|
||||
|
||||
.INPUTS
|
||||
<None>
|
||||
|
||||
.OUTPUTS
|
||||
<Test results>
|
||||
|
||||
.NOTES
|
||||
Requires Pester (PowerShell Testing Framework). Download from
|
||||
https://github.com/pester/Pester
|
||||
or use Chocolatey (https://chocolatey.org/)
|
||||
choco install pester
|
||||
or use PsGet (http://psget.net/)
|
||||
Install-Module Pester
|
||||
to install
|
||||
#>
|
||||
$ModuleUnderTest = "InstallationSDK.ChefClientInstaller"
|
||||
$PSProjDepedencies = @(
|
||||
Join-Path $PSScriptRoot "..\InstallationSDK.AzureServiceRuntime\InstallationSDK.AzureServiceRuntime.psproj"
|
||||
)
|
||||
$ModuleDependencies = @()
|
||||
$ErrorActionPreference = "STOP"
|
||||
|
||||
$modulesToCleanup = @($ModuleUnderTest)
|
||||
|
||||
|
||||
Import-Module Pester
|
||||
Import-Module Powershellution
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
function WriteVerbose
|
||||
{
|
||||
param($msg)
|
||||
|
||||
Write-Verbose ("{0}: {1}" -f (Split-Path -Leaf $MyInvocation.ScriptName),$msg)
|
||||
}
|
||||
|
||||
function ImportModuleAndToCleanupList
|
||||
{
|
||||
param($module)
|
||||
|
||||
Import-Module $module
|
||||
$script:modulesToCleanup += $module
|
||||
}
|
||||
|
||||
function SafeAddModuleFolderToPSRoot
|
||||
{
|
||||
param($moduleFolder)
|
||||
|
||||
if (-not $env:PSModulePath.Contains($moduleFolder))
|
||||
{
|
||||
$env:PSModulePath += ";$moduleFolder"
|
||||
}
|
||||
}
|
||||
|
||||
function Cleanup
|
||||
{
|
||||
foreach($module in $modulesToCleanup)
|
||||
{
|
||||
Remove-Module $module -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Eh, sometimes Pester fails with the error that TestDrive already exists.. clear it for them
|
||||
Remove-Variable -Name TestDrive -Force -Scope Script -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Setup
|
||||
|
||||
#
|
||||
# Ensure clean environment
|
||||
#
|
||||
WriteVerbose "Remove modules to ensure a clean environment"
|
||||
Cleanup
|
||||
|
||||
#
|
||||
# Build Dependent psprojs
|
||||
#
|
||||
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"
|
||||
|
||||
SafeAddModuleFolderToPSRoot $moduleFolder
|
||||
ImportModuleAndToCleanupList $moduleName
|
||||
}
|
||||
|
||||
#
|
||||
# Build Module Under Test
|
||||
#
|
||||
WriteVerbose "Building the Module Under Test"
|
||||
$psprojFile = Resolve-Path (Join-Path $PSScriptRoot "*.psproj")
|
||||
Write-Verbose "Building psproj: $psprojFile"
|
||||
New-ProjectBuild -Path $psprojFile
|
||||
|
||||
#
|
||||
# Loading Module-Under-Test
|
||||
#
|
||||
WriteVerbose "Loading Module-Under-Test"
|
||||
$ModuleRoot = Join-Path $PSScriptRoot "bin"
|
||||
SafeAddModuleFolderToPSRoot $moduleRoot
|
||||
Import-Module $ModuleUnderTest
|
||||
|
||||
#endregion
|
||||
|
||||
#
|
||||
# Ready to run tests!
|
||||
#
|
||||
InModuleScope $ModuleUnderTest {
|
||||
Describe "Get-ChefClientConfig" {
|
||||
$tempFile = "TestDrive:\test.txt"
|
||||
|
||||
It "Should parse and return an object" {
|
||||
"log_level :info" | Set-Content $tempFile
|
||||
$config = Get-ChefClientConfig -Path $tempFile
|
||||
$config.log_level | Should Be ":info"
|
||||
}
|
||||
|
||||
It "Should parse a value with spaces in them and return an object" {
|
||||
"cache_path 'C:\Some Path With Spaces''" | Set-Content $tempFile
|
||||
$config = Get-ChefClientConfig -Path $tempFile
|
||||
$config.cache_path | Should Be "C:\Some Path With Spaces"
|
||||
}
|
||||
|
||||
Context "Given no arguments" {
|
||||
It "Should return an empty object" {
|
||||
$config = Get-ChefClientConfig
|
||||
$members = $config | Get-Member -MemberType NoteProperty | Select-Object -Property Name
|
||||
$members.Count -gt 0 | Should Be $true
|
||||
($members | where { $_.Name -eq "node_name" }).Name | Should BeExactly "node_name"
|
||||
$config.node_name | Should BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Save-ChefClientConfig" {
|
||||
$tempFile = "TestDrive:\test.txt"
|
||||
$newFile = "TestDrive:\new.txt"
|
||||
$templateFile = "TestDrive:\template.rb"
|
||||
|
||||
"cache_path 'C:\chef'`nlog_level :info`ninterval 100" | Set-Content $tempFile
|
||||
"now = Time.new`n`nlog_location `"c:/chef/client_`" + now.strftime(`"%Y%m%d`") + `".log`"`nlog_level :info`ninterval 999" | Set-Content $templateFile
|
||||
Get-ChefClientConfig -Path $tempFile | Save-ChefClientConfig -Path $newFile
|
||||
|
||||
It "Should save a properly formatted file" {
|
||||
$newFile | Should Exist
|
||||
$newFile | Should Contain "cache_path\s+'C:\\chef'"
|
||||
}
|
||||
|
||||
It "Should check that symbols are not quoted" {
|
||||
$newFile | Should Exist
|
||||
$newFile | Should Contain "['`"][^:]"
|
||||
$newFile | Should Contain "[^']:"
|
||||
}
|
||||
|
||||
It "Should check that numbers are not quoted" {
|
||||
$newFile | Should Exist
|
||||
$newFile | Should Contain "\s[^'][0-9]"
|
||||
}
|
||||
|
||||
It "Should overwrite when -Overwrite is used" {
|
||||
# overwrite new file with an empty file, so we can properly check the contents
|
||||
New-Item $newFile -ItemType File -Force
|
||||
{ Get-ChefClientConfig -Path $tempFile | Save-ChefClientConfig -Path $newFile } | Should Throw
|
||||
{ Get-ChefClientConfig -Path $tempFile | Save-ChefClientConfig -Path $newFile -Overwrite } | Should Not Throw
|
||||
$newFile | Should Exist
|
||||
$newFile | Should Contain "cache_path\s+'C:\\chef'"
|
||||
}
|
||||
|
||||
It "Should append to an existing file" {
|
||||
Get-ChefClientConfig -Path $tempFile | Save-ChefClientConfig -Path $templateFile -Append
|
||||
$templateFile | Should Exist
|
||||
$templateFile | Should Contain "now = Time\.new"
|
||||
$templateFile | Should Contain ([regex]::Escape("log_location `"c:/chef/client_`" + now.strftime(`"%Y%m%d`") + `".log`""))
|
||||
$templateFile | Should Contain "interval\s*100"
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Get-ChefNodeList" {
|
||||
Context "Verify Command Line if no client config" {
|
||||
Mock Invoke-Knife { "knife $args" }
|
||||
|
||||
It "Should execute 'knife node list' if there's no client" {
|
||||
$commandLine = (Get-ChefNodeList).Trim()
|
||||
$commandLine | Should BeExactly "knife node list"
|
||||
}
|
||||
}
|
||||
|
||||
Context "Verify Command Line if client config is specified" {
|
||||
Mock Invoke-Knife { "knife $args" }
|
||||
|
||||
$configFile = "TestDrive:\Config.rb"
|
||||
"chef_server_url 'http://localhost/organizations/msn'" | Set-Content $configFile
|
||||
$configObject = Get-ChefClientConfig -Path $configFile
|
||||
|
||||
It "Should execute 'knife node list -c client.rb' if given the config file path" {
|
||||
$commandLine = (Get-ChefNodeList -Config $configFile).Trim()
|
||||
$commandLine | Should Match ("knife node list -c {0}" -f [regex]::Escape($configFile))
|
||||
}
|
||||
|
||||
It "Should execute 'knife node list -c client.rb' if given the config object" {
|
||||
$commandLine = (Get-ChefNodeList -Config $configObject).Trim()
|
||||
$commandLine | Should Match "knife node list -c .*"
|
||||
}
|
||||
|
||||
It "Should fail if config does not exist" {
|
||||
{ Get-ChefNodeList -Config "DoesNotExist" } | Should Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Returns as expected" {
|
||||
Mock Invoke-Knife { return @("SomeNode_0","SomeNode_1","SomeNode_2") }
|
||||
$nodes = Get-ChefNodeList
|
||||
|
||||
It "Should return a list of nodes" {
|
||||
$nodes | Should Be @("SomeNode_0","SomeNode_1","SomeNode_2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Install-ChefClient" {
|
||||
Mock Start-Process {param($FilePath, $ArgumentList) "$FilePath $ArgumentList" }
|
||||
Mock Invoke-SC { "sc.exe $args" }
|
||||
Mock Set-Path { "$env:path;$args" }
|
||||
|
||||
$InstallLocation = "TestDrive:\Opscode"
|
||||
$RootPath = "TestDrive:\Chef"
|
||||
$commandLinesToExecute = Install-ChefClient -InstallLocation $InstallLocation -RootPath $RootPath
|
||||
|
||||
$msiExecCommand = $commandLinesToExecute[0]
|
||||
$scFailureCommand = $commandLinesToExecute[1]
|
||||
$scConfigCommand = $commandLinesToExecute[2]
|
||||
$path = $commandLinesToExecute[3]
|
||||
It "(msiexec) should contain everything it needs to install the chef-client properly" {
|
||||
$msiExecCommand | Should Match "msiexec"
|
||||
$msiExecCommand | Should Match "\.msi"
|
||||
$msiExecCommand | Should Match "ChefClientFeature,ChefServiceFeature"
|
||||
$msiExecCommand | Should Match "InstallLocation"
|
||||
$msiExecCommand | Should Match "ROOTDRIVE"
|
||||
}
|
||||
|
||||
It "(sc) should set the reset on failure settings properly" {
|
||||
$scFailureCommand | Should Match "sc.exe"
|
||||
$scFailureCommand | Should Match "failure"
|
||||
$scFailureCommand | Should Match "chef-client reset= [0-9]* actions= restart/[0-9]*"
|
||||
}
|
||||
|
||||
It "(sc) should configure the service with the binpath" {
|
||||
$rubyPath = "$InstallLocation\chef\embedded\bin\ruby.exe"
|
||||
$servicePath = "$InstallLocation\chef\bin\chef-windows-service"
|
||||
$scConfigCommand | Should Match "sc.exe"
|
||||
$scConfigCommand | Should Match "config"
|
||||
$scConfigCommand | Should Match ("chef-client binpath= `"{0} {1} -c {2} -l {3}`"" -f
|
||||
[regex]::Escape($rubyPath),
|
||||
[regex]::Escape($servicePath),
|
||||
[regex]::Escape("$RootPath\Client.rb"),
|
||||
[regex]::Escape("$RootPath\client.log"))
|
||||
}
|
||||
|
||||
It "Path environment variable should installation directory" {
|
||||
$path | Should Match ([Regex]::Escape("$InstallLocation\chef\bin"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Clean-up
|
||||
#
|
||||
WriteVerbose "DONE! Cleaning up"
|
||||
Cleanup
|
|
@ -3,15 +3,6 @@
|
|||
$ErrorActionPreference = "Stop"
|
||||
$VerbosePreference = "Continue"
|
||||
|
||||
pushd $PSScriptRoot
|
||||
try
|
||||
{
|
||||
$msi = (resolve-path "./resources/*msi").path
|
||||
}
|
||||
finally
|
||||
{
|
||||
popd
|
||||
}
|
||||
|
||||
function Install-ChefClient
|
||||
{
|
||||
|
@ -29,18 +20,280 @@ function Install-ChefClient
|
|||
Install-ChefClient -verbose
|
||||
#>
|
||||
|
||||
param(
|
||||
$InstallLocation = "C:\Opscode",
|
||||
$RootDrive = $env:SystemDrive,
|
||||
$RootPath = "C:\Chef",
|
||||
$ConfigFile = "Client.rb",
|
||||
$LogFile = "client.log"
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
# TODO - change the location of the client root via chef-client configuration so that we don't install on the System Drive.
|
||||
md d:\chef -ErrorAction SilentlyContinue
|
||||
copy-item .\*.rb d:\chef
|
||||
copy-item .\*.pem d:\chef
|
||||
write-verbose $PSScriptRoot
|
||||
# chef repository
|
||||
md $RootPath -ErrorAction SilentlyContinue | Out-Null
|
||||
|
||||
$pathToConfigFile = Join-Path $RootPath $ConfigFile
|
||||
$pathToLogFile = Join-Path $RootPath $LogFile
|
||||
|
||||
$msi = (Resolve-Path (Join-Path $PSScriptRoot "\resources\*.msi")).Path
|
||||
write-verbose "Installing chef client from $msi."
|
||||
start-process msiexec -ArgumentList "/qn /i $msi ADDLOCAL=`"ChefClientFeature,ChefServiceFeature`" /log $($msi).log" -Wait
|
||||
sc.exe failure "chef-client" reset= 86400 actions= restart/5000
|
||||
start-service chef-client
|
||||
start-process msiexec -ArgumentList "/qn /i $msi ADDLOCAL=`"ChefClientFeature,ChefServiceFeature`" InstallLocation=`"$InstallLocation`" ROOTDRIVE=`"$RootDrive\`" /L $($msi).log" -Wait
|
||||
Invoke-SC failure "chef-client" reset= 86400 actions= restart/5000
|
||||
|
||||
$rubyPath = Join-Path $InstallLocation "\chef\embedded\bin\ruby.exe"
|
||||
$servicePath = Join-Path $InstallLocation "\chef\bin\chef-windows-service"
|
||||
# Add the --Config and --LogFile paramters to the binpath of the service
|
||||
Invoke-SC config "chef-client" binpath= "`"$rubyPath $servicePath -c $pathToConfigFile -l $pathToLogFile`""
|
||||
|
||||
# For running knife commands in the same process as the installer. The installer sets the path variable but in a different context, so we can't run knife otherwise.
|
||||
Set-Path (Join-Path $InstallLocation "chef\bin")
|
||||
}
|
||||
}
|
||||
|
||||
Export-ModuleMember Install-ChefClient
|
||||
function Get-ChefClientConfig
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Loads the Chef Client Config into memory
|
||||
|
||||
.DESCRIPTION
|
||||
Loads the Chef Client Config for itempotency and easy editing of config values
|
||||
|
||||
.EXAMPLE
|
||||
Get-ChefClientConfig -Path .\Client.rb
|
||||
|
||||
.EXAMPLE
|
||||
Get-ChefClientConfig
|
||||
#>
|
||||
param(
|
||||
$Path = $null
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
$InitialProperties = @{
|
||||
"log_level" = "";
|
||||
"log_location" = "";
|
||||
"cache_path" = "";
|
||||
"client_key" = "";
|
||||
"node_name" = "";
|
||||
"chef_server_url" = "";
|
||||
"validation_client_name" = "";
|
||||
"validation_key" = "";
|
||||
"interval" = "";
|
||||
"json_attribs" = "";
|
||||
"ssl_verify_mode" = ""}
|
||||
|
||||
if ($Path)
|
||||
{
|
||||
# Regex matches simple "somekey value" pattern
|
||||
# Parses out custom Ruby (like now = Time.new)
|
||||
# Use -Template in Save-ChefClientConfig to get back custom ruby script
|
||||
# (Side note, I don't really like this implemention. Probably needs to be replaced with something more robust)
|
||||
Get-Content -Path $Path | foreach {
|
||||
if (-not ($_ -match "^[a-zA-Z0-9_]*\s*[:'`"0-9].*['`"]?" ))
|
||||
{
|
||||
# Ran into this scenario before
|
||||
Write-Warning "The configuration might contain some unsupported values for this cmdlet. When piped into the Save-ChefClientConfig function, ensure the values are properly set"
|
||||
}
|
||||
|
||||
$tokens = $_.Split(@(" ", "`t"), "RemoveEmptyEntries")
|
||||
$key = $tokens[0]
|
||||
$value = ($tokens | Select-Object -Last ($tokens.Count - 1)) -join " "
|
||||
$InitialProperties.$key = $value.Trim("'", '"');
|
||||
}
|
||||
}
|
||||
|
||||
$InitialProperties.Add("AdditionalProperties", @{})
|
||||
New-Object -TypeName PSObject -Property $InitialProperties
|
||||
}
|
||||
}
|
||||
|
||||
function Save-ChefClientConfig
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Save the Chef Client Config object to a file
|
||||
|
||||
.DESCRIPTION
|
||||
Takes a dictionary or PSObject and writes a Client.rb file for chef to use
|
||||
|
||||
.EXAMPLE
|
||||
Save-ChefClientConfig -Path .\Client.rb
|
||||
|
||||
.EXAMPLE
|
||||
Save-ChefClientConfig -Path .\Client.rb -Append
|
||||
|
||||
.EXAMPLE
|
||||
Save-ChefClientConfig -Path .\Client.rb -Overwrite
|
||||
|
||||
.EXAMPLE
|
||||
Save-ChefClientConfig
|
||||
#>
|
||||
[CmdLetBinding(DefaultParameterSetName="Append")]
|
||||
param(
|
||||
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
|
||||
$InputObject,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
$Path = $null,
|
||||
|
||||
$Template = $null,
|
||||
|
||||
[Parameter(ParameterSetName="Append")]
|
||||
[switch]$Append = $false,
|
||||
|
||||
[Parameter(ParameterSetName="Overwrite")]
|
||||
[switch]$Overwrite = $false
|
||||
)
|
||||
|
||||
|
||||
Process
|
||||
{
|
||||
if (Test-Path $Path)
|
||||
{
|
||||
if ($Overwrite)
|
||||
{
|
||||
Remove-Item $Path -Force
|
||||
}
|
||||
elseif (-not $Append)
|
||||
{
|
||||
throw "Cannot save chef Client.rb to '$Path' because the file already exists. Use -Overwrite to overwrite or -Append to add"
|
||||
}
|
||||
}
|
||||
|
||||
$hashToWrite = @{}
|
||||
if ($InputObject -is [HashTable])
|
||||
{
|
||||
$hashToWrite = $InputObject
|
||||
}
|
||||
elseif ($InputObject -is [PSObject])
|
||||
{
|
||||
$InputObject | Get-Member -MemberType NoteProperty | where { $_.Name -ne "AdditionalProperties" } | foreach {
|
||||
$hashToWrite.$($_.Name) = $InputObject.$($_.Name)
|
||||
}
|
||||
|
||||
# Overwrite with anything in the AdditionalProperties property
|
||||
$InputObject.AdditionalProperties.Keys | foreach {
|
||||
$hashToWrite.$_ = $InputObject.AdditionalProperties.$_
|
||||
}
|
||||
}
|
||||
|
||||
$contents = @()
|
||||
if ($Append)
|
||||
{
|
||||
$contents = Get-Content -Path $Path
|
||||
}
|
||||
|
||||
# TODO: Could use some optimization
|
||||
$hashToWrite.Keys | where { $hashToWrite[$_] } | foreach {
|
||||
$key = $_
|
||||
$value = $hashToWrite[$key]
|
||||
|
||||
# symbols and numbers should not be surrounded in quotes
|
||||
if (-not $value.StartsWith(":") -and -not ($value -match "^[0-9.]*$"))
|
||||
{
|
||||
$value = "'$value'"
|
||||
}
|
||||
|
||||
$newLineToAdd = "{0} {1}" -f $key,$value
|
||||
|
||||
# Check if the line already exists in the file (i.e. if we're just appending), if so replace that line, rather than add a new line
|
||||
# TODO: More optimization. -Match and -Replace could do two iterations
|
||||
if ($contents -match "^$key\s")
|
||||
{
|
||||
$contents = $contents -replace "^$key\s.*$",$newLineToAdd
|
||||
}
|
||||
else
|
||||
{
|
||||
$contents += $newLineToAdd
|
||||
}
|
||||
}
|
||||
|
||||
$contents -join "`n" | Set-Content $Path
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ChefNodeList
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets the list of nodes from the chef server
|
||||
|
||||
.DESCRIPTION
|
||||
Uses knife to connect to the server and retrieve the node list
|
||||
|
||||
.EXAMPLE
|
||||
Get-ChefNodeList -Config .\Client.rb
|
||||
|
||||
.EXAMPLE
|
||||
Get-ChefNodeList -Config $configObject
|
||||
|
||||
.EXAMPLE
|
||||
Get-ChefNodeList
|
||||
#>
|
||||
|
||||
param(
|
||||
$Config
|
||||
)
|
||||
|
||||
$TemporaryConfigFile = $null
|
||||
$ConfigArgument = $null
|
||||
|
||||
try
|
||||
{
|
||||
if ($Config)
|
||||
{
|
||||
if ($Config -is [PSObject])
|
||||
{
|
||||
$TemporaryConfigFile = [IO.Path]::GetRandomFileName()
|
||||
Save-ChefClientConfig -InputObject $Config -Path $TemporaryConfigFile
|
||||
$Config = $TemporaryConfigFile
|
||||
}
|
||||
elseif (-not (Test-Path $Config))
|
||||
{
|
||||
throw "$config is not a valid config object or path to a config file"
|
||||
}
|
||||
|
||||
$ConfigArgument = "-c"
|
||||
}
|
||||
|
||||
Invoke-Knife node list $ConfigArgument $Config
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($TemporaryConfigFile)
|
||||
{
|
||||
Remove-Item -Path $TemporaryConfigFile -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 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
|
||||
{
|
||||
knife $args
|
||||
}
|
||||
|
||||
function Invoke-SC
|
||||
{
|
||||
sc.exe $args
|
||||
}
|
||||
|
||||
|
||||
# Same with env:path
|
||||
function Set-Path
|
||||
{
|
||||
param($newPath)
|
||||
|
||||
if (-not $env:Path.Contains($newPath))
|
||||
{
|
||||
$env:Path += ";$newPath;"
|
||||
}
|
||||
}
|
||||
|
||||
Export-ModuleMember Install-ChefClient
|
||||
Export-ModuleMember Get-ChefClientConfig
|
||||
Export-ModuleMember Save-ChefClientConfig
|
||||
Export-ModuleMember Get-ChefNodeList
|
|
@ -10,10 +10,10 @@
|
|||
"Description": "PowerShell Cmdlets for installing Chef-Client.",
|
||||
"Copyright": "(c) 2014 Microsoft. All rights reserved.",
|
||||
"PowerShellVersion": "3.0",
|
||||
"RequiredModules": []
|
||||
"RequiredModules": ["InstallationSDK.AzureServiceRuntime"]
|
||||
},
|
||||
"Files": {
|
||||
"RootModule": "InstallationSDK.ChefClientInstaller.psm1",
|
||||
"Content": [ "./resources/*.msi"]
|
||||
"Content": [ "./resources/*.*", "./code/*.*", "./script/*.*" ]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="ClientService.cs" company="Microsoft Corporation">
|
||||
// Copyright (C) Microsoft. All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
namespace Microsoft.OnlinePublishing.Chef
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.ServiceProcess;
|
||||
|
||||
/// <summary>
|
||||
/// The Chef.ClientService Start and Stop methods should be called in the Roles OnStart and OnStop methods respectively.
|
||||
/// </summary>
|
||||
public static class ClientService
|
||||
{
|
||||
/// <summary>
|
||||
/// Stop the Chef Client windows service with the default wait time of 1 minute.
|
||||
/// </summary>
|
||||
public static void Stop()
|
||||
{
|
||||
Stop(new TimeSpan(0, 1, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the Chef Client windows service.
|
||||
/// </summary>
|
||||
/// <param name="timeToWait">Wait time for operation to complete.</param>
|
||||
public static void Stop(TimeSpan timeToWait)
|
||||
{
|
||||
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 alloted [{0}].", timeToWait);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
Trace.TraceInformation("Chef Client - Invalid Operation, is the role running with elevated privledges. Ex:{0}.", e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start Chef Client windows service.
|
||||
/// </summary>
|
||||
public static void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
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 privledges. Ex:{0}.", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
|
@ -0,0 +1,204 @@
|
|||
param(
|
||||
[String]$ClientRb = "Client.rb",
|
||||
[string]$ClientPem = "client.pem",
|
||||
[string]$RootPath = "C:\chef\"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Write-Output "Script:main.ps1 starting"
|
||||
|
||||
$pathToClientPem = Join-path -Path $RootPath -ChildPath $ClientPem
|
||||
$pathToClientRb = Join-path -Path $RootPath -ChildPath $ClientRb
|
||||
|
||||
if (Test-Path $PathToClientPem)
|
||||
{
|
||||
Write-Output "Script:main.ps1 $pathToClientPem already exists. Nothing to do. Exiting"
|
||||
return
|
||||
}
|
||||
|
||||
# Add approot module path
|
||||
$modulePath = resolve-Path -Path $PSScriptRoot\Modules
|
||||
if(-not $env:PSModulePath.Contains($modulePath.Path))
|
||||
{
|
||||
$env:PSModulePath += (";" + $modulePath.Path)
|
||||
Write-Output "PSModulePath: $env:PSModulePath"
|
||||
}
|
||||
|
||||
# Insert your custom logic below...
|
||||
|
||||
# Import modules
|
||||
Import-Module InstallationSDK.ChefClientInstaller
|
||||
Import-Module InstallationSDK.AzureServiceRuntime
|
||||
|
||||
# Go ahead and install, we'll need the knife tool later
|
||||
# Override the rootdrive, for Azure
|
||||
Install-ChefClient -RootDrive "C:" -RootPath $RootPath
|
||||
|
||||
$ClientRbObject = $null
|
||||
|
||||
# Get template Client.rb file, if it exists
|
||||
$TemplateClientRb = Join-Path $PSScriptRoot $ClientRb
|
||||
|
||||
$ClientRbObject = Get-ChefClientConfig
|
||||
|
||||
# Set node name. Format: [cloud service name]_IN_[Azure instance number]
|
||||
Get-CloudServiceRoleInstance -ErrorAction Continue | out-Null
|
||||
$roleInstance = Get-CloudServiceRoleInstance
|
||||
$config = ConvertFrom-Json ((Get-Content $PSScriptRoot\config.json -ErrorAction SilentlyContinue) -join "`n")
|
||||
$name = $roleInstance.DeploymentID
|
||||
$roleName = $roleInstance.Id.Substring(0, $roleInstance.Id.IndexOf("_"))
|
||||
if ($config -and $config.name)
|
||||
{
|
||||
$name = $config.name
|
||||
}
|
||||
|
||||
# Add Instance number to the node name
|
||||
Write-Output "Role Instance Name: $($roleInstance.Id)"
|
||||
$nodeName = $($roleInstance.Id).Replace($roleName, $name)
|
||||
|
||||
# Set ssl_verify_mode on Client.rb. (As of Chef 12, it defaults to :verify_peer).
|
||||
# For Non-Production ONLY: Unless a valid certificate is set, need to use :verify_none
|
||||
if ($config -and $config.sslVerifyMode)
|
||||
{
|
||||
$sslVerifyMode = $config.sslVerifyMode
|
||||
$ClientRbObject.ssl_verify_mode = $sslVerifyMode
|
||||
Write-Output "Set ssl_verify_mode to: $sslVerifyMode"
|
||||
if ($sslVerifyMode -eq ":verify_none")
|
||||
{
|
||||
Write-Warning "`"ssl_verify_mode: '$sslVerifyMode'`" should only be used for testing purposes only!"
|
||||
}
|
||||
}
|
||||
|
||||
# Try to get server_url from Cloud Service CsCfg first. If not, check the config.json
|
||||
# Value from Cloud Service CsCfg always wins.
|
||||
$url = Get-CloudServiceConfigurationSettingValue "ChefClient_ServerUrl"
|
||||
if (-not $url -and ($config -and $config.serverUrl))
|
||||
{
|
||||
$url = $config.serverUrl
|
||||
}
|
||||
|
||||
if ($url)
|
||||
{
|
||||
$ClientRbObject.chef_server_url = $url
|
||||
Write-Output "Set chef url to: $url"
|
||||
|
||||
#
|
||||
# If we're connecting to the server. Client Name and Validation Key needs to be set
|
||||
#
|
||||
|
||||
# Validation client name is now on a per-org basis
|
||||
if ($config -and $config.ValidationClientName)
|
||||
{
|
||||
$validationClientName = $config.ValidationClientName
|
||||
$ClientRbObject.validation_client_name = $validationClientName
|
||||
Write-Output "Set Validation Client Name to '$validationClientName'"
|
||||
|
||||
# Temporarily set node name to validation client name. This is so we can call node list and determine what names are available
|
||||
$ClientRbObject.node_name = $validationClientName
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Validation client name must be set if serverUrl is defined"
|
||||
}
|
||||
|
||||
# Try to get validationKey from Cloud Service CsCfg first. If not, check the config.json
|
||||
# Value from Cloud Service CsCfg always wins.
|
||||
$validationKey = Get-CloudServiceConfigurationSettingValue "ChefClient_ValidationKey"
|
||||
if (-not $validationKey -and ($config -and $config.validationKey))
|
||||
{
|
||||
$validationKey = $config.validationKey
|
||||
}
|
||||
|
||||
if ($validationKey)
|
||||
{
|
||||
# Ensure the key exists with that filename
|
||||
$validationKeyTemp = Join-Path $PSScriptRoot $validationKey
|
||||
if (-not (Test-Path $validationKeyTemp))
|
||||
{
|
||||
throw "Did not find validation key at path $(Join-Path $PSScriptRoot $validationKey)"
|
||||
}
|
||||
|
||||
$pathToValidationKey = Join-Path $RootPath $validationKey
|
||||
|
||||
Copy-Item $validationKeyTemp $pathToValidationKey -Force
|
||||
$ClientRBObject.validation_key = $pathToValidationKey
|
||||
Write-Output "Set validation key to '$pathToValidationKey'"
|
||||
|
||||
# Temporarily set node name to validation client name. This is so we can call node list and determine what names are available
|
||||
$ClientRbObject.client_key = $pathToValidationKey
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Validation key must be set if serverUrl is defined"
|
||||
}
|
||||
|
||||
#
|
||||
# Check if the node's name is available
|
||||
#
|
||||
$tempConfigFile = $null
|
||||
try
|
||||
{
|
||||
$tempConfigFile = [IO.Path]::GetRandomFileName()
|
||||
Copy-Item -Path $TemplateClientRb -Destination $tempConfigFile
|
||||
Save-ChefClientConfig -InputObject $ClientRbObject -Path $tempConfigFile -Append
|
||||
$nodes = Get-ChefNodeList -Config $tempConfigFile
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($tempConfigFile)
|
||||
{
|
||||
Remove-Item -Path $tempConfigFile -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# increment the node name until one exists that doesn't conflict
|
||||
$baseName = $nodeName
|
||||
for($i = 1; $nodes.Contains($nodeName) ;$i++)
|
||||
{
|
||||
$nodeName = "$baseName{0}" -f ".$i"
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Output "chef url not set in configuration file. Node will not register with Chef Server."
|
||||
}
|
||||
|
||||
# Alter client.rb with new node name
|
||||
$ClientRbObject.node_name = $nodeName
|
||||
Write-Output "Set chef node_name to: $nodeName"
|
||||
$ClientRbObject.client_key = $pathToClientPem
|
||||
Write-Output "Set chef client_key to: $pathToClientRb"
|
||||
|
||||
if ($config -and $config.pollInterval)
|
||||
{
|
||||
$interval = $config.pollInterval
|
||||
$ClientRbObject.interval = $interval
|
||||
Write-Output "Set poll interval to: $interval seconds"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Output "Poll Interval not set. Default value (if not set) is 1800s (30m)."
|
||||
}
|
||||
|
||||
# Create first-run-bootstrap.json to register new node with Chef Server
|
||||
if ($config -and $config.role)
|
||||
{
|
||||
# Register with the correct update domain role [role name]
|
||||
$chefRole = $($config.role)
|
||||
$bootStrapperFile = "first-run-bootstrap.json"
|
||||
$bootStrapper = "{`r`n `"run_list`": [ `"role[$chefRole]`" ]`r`n}"
|
||||
Write-Output "Setting bootstrap content: $bootStrapper"
|
||||
|
||||
$pathToBootStrapper = Join-Path $RootPath $bootStrapperFile
|
||||
$bootStrapper | Out-File $pathToBootStrapper -Encoding ascii
|
||||
$ClientRbObject.json_attribs = $pathToBootStrapper
|
||||
|
||||
Write-Output "Set bootstrapper path to: '$pathToBootStrapper'"
|
||||
}
|
||||
|
||||
Copy-Item -Path $TemplateClientRb -Destination $pathToClientRb -Force
|
||||
$ClientRbObject | Save-ChefClientConfig -Path $pathToClientRb -Append
|
||||
|
||||
start-service chef-client
|
||||
|
||||
Write-Output "Script:main.ps1 exiting"
|
|
@ -0,0 +1,5 @@
|
|||
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'
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "chefClient",
|
||||
"role": "",
|
||||
"pollInterval": "",
|
||||
"serverUrl": "",
|
||||
"sslVerifyMode": ":verify_none",
|
||||
"validationClientName": "",
|
||||
"validationKey": ""
|
||||
}
|
Загрузка…
Ссылка в новой задаче