Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • ITI/foreman_wds
1 result
Show changes
Commits on Source (3)
...@@ -49,20 +49,19 @@ class WdsServer < ApplicationRecord ...@@ -49,20 +49,19 @@ class WdsServer < ApplicationRecord
objects = run_wql('SELECT * FROM MSFT_WdsClient', on_error: {})[:msft_wdsclient] objects = run_wql('SELECT * FROM MSFT_WdsClient', on_error: {})[:msft_wdsclient]
objects = nil if objects&.empty? objects = nil if objects&.empty?
objects ||= begin objects ||= begin
data = connection.shell(:powershell) do |s| clients = run_pwsh('Get-WdsClient').stdout
s.run('Get-WdsClient | ConvertTo-Json -Compress') clients = '[]' if clients.empty?
end.stdout underscore_result([JSON.parse(clients)].flatten)
data = '[]' if data.empty?
underscore_result([JSON.parse(data)].flatten)
end end
objects objects
end end
def client(host) def client(host)
device_ids = [host.mac.upcase.tr(':', '-'), host.name]
device_names = [host.name, host.shortname]
clients.find do |c| clients.find do |c|
[host.mac.upcase.tr(':', '-'), host.name].include?(c[:device_id]) || [host.name, host.shortname].include?(c[:device_name]) device_ids.include?(c[:device_id]) || device_names.include?(c[:device_name])
end end
end end
...@@ -82,18 +81,27 @@ class WdsServer < ApplicationRecord ...@@ -82,18 +81,27 @@ class WdsServer < ApplicationRecord
raise NotImplementedError, 'Not finished yet' raise NotImplementedError, 'Not finished yet'
ensure_unattend(host) ensure_unattend(host)
connection.shell(:powershell) do |sh| run_pwsh [
sh.run("New-WdsClient -DeviceID '#{host.mac.upcase.delete ':'}' -DeviceName '#{host.name}' -WdsClientUnattend '#{unattend_file(host)}' -BootImagePath 'boot\\#{wdsify_architecture(host.architecture)}\\images\\#{(host.wds_boot_image || boot_images.first).file_name}' -PxePromptPolicy 'NoPrompt'") 'New-WdsClient',
end "-DeviceID '#{host.mac.upcase.delete ':'}'",
"-DeviceName '#{host.name}'",
"-WdsClientUnattend '#{unattend_file(host)}'",
'-BootImagePath',
[
'boot',
wdsify_architecture(host.architecture),
'images',
(host.wds_boot_image || boot_images.first).file_name
].join('\\').then { |path| "'#{path}'" },
"-PxePromptPolicy 'NoPrompt'"
].join(' ')
end end
def delete_client(host) def delete_client(host)
raise NotImplementedError, 'Not finished yet' raise NotImplementedError, 'Not finished yet'
delete_unattend(host) delete_unattend(host)
connection.shell(:powershell) do |sh| run_pwsh("Remove-WdsClient -DeviceID '#{host.mac.upcase.delete ':'}'", json: false)
sh.run("Remove-WdsClient -DeviceID '#{host.mac.upcase.delete ':'}'")
end
end end
def all_images def all_images
...@@ -162,9 +170,17 @@ class WdsServer < ApplicationRecord ...@@ -162,9 +170,17 @@ class WdsServer < ApplicationRecord
def unattend_path def unattend_path
cache.cache(:unattend_path) do cache.cache(:unattend_path) do
JSON.parse(connection.shell(:powershell) do |sh| JSON.parse(
sh.run('Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\WDSServer\Providers\WDSTFTP -Name RootFolder | select RootFolder | ConvertTo-Json -Compress') run_pwsh(
end, symbolize_names: true)[:RootFolder] [
'Get-ItemProperty',
' -Path HKLM:\SYSTEM\CurrentControlSet\Services\WDSServer\Providers\WDSTFTP',
' -Name RootFolder',
'| select RootFolder'
].map(&:strip).join(' ')
),
symbolize_names: true
)[:RootFolder]
end end
end end
...@@ -187,6 +203,7 @@ class WdsServer < ApplicationRecord ...@@ -187,6 +203,7 @@ class WdsServer < ApplicationRecord
raise 'No provisioning interface available' unless iface raise 'No provisioning interface available' unless iface
raise NotImplementedException, 'TODO: Not implemented yet' raise NotImplementedException, 'TODO: Not implemented yet'
raise NotImplementedException, 'TODO: Not implemented yet' if SETTINGS[:wds_unattend_group]
# TODO: render template, send as heredoc # TODO: render template, send as heredoc
template = host.operatingsystem.provisioning_templates.find { |t| t.template_kind.name == 'wds_unattend' } template = host.operatingsystem.provisioning_templates.find { |t| t.template_kind.name == 'wds_unattend' }
...@@ -198,33 +215,53 @@ class WdsServer < ApplicationRecord ...@@ -198,33 +215,53 @@ class WdsServer < ApplicationRecord
template_data = host.render_template template: template template_data = host.render_template template: template
connection.shell(:powershell) do |sh| file_path = unattend_file(host)
file_path = unattend_file(host) script = []
script << "$unattend_render = @'\n#{template_data}\n'@"
sh.run("$unattend_render = @'\n#{template_data}\n'@") script << "New-Item -Path '#{file_path}' -ItemType 'file' -Value $unattend_render"
sh.run("New-Item -Path '#{file_path}' -ItemType 'file' -Value $unattend_render")
source_image = host.wds_facet.install_image
source_image = host.wds_facet.install_image target_image = target_image_for(host)
target_image = target_image_for(host)
if SETTINGS[:wds_unattend_group] if SETTINGS[:wds_unattend_group]
raise NotImplementedException, 'TODO: Not implemented yet' # New-WdsInstallImageGroup -Name #{SETTINGS[:wds_unattend_group]}
# New-WdsInstallImageGroup -Name #{SETTINGS[:wds_unattend_group]} # Export-WdsInstallImage -ImageGroup <Group> ...
# Export-WdsInstallImage -ImageGroup <Group> ... # Import-WdsInstallImage -ImageGroup #{SETTINGS[:wds_unattend_group]} -UnattendFile '#{file_path}' -OverwriteUnattend ...
# Import-WdsInstallImage -ImageGroup #{SETTINGS[:wds_unattend_group]} -UnattendFile '#{file_path}' -OverwriteUnattend ... else
else script << [
sh.run("Copy-WdsInstallImage -ImageGroup '#{source_image.image_group}' -FileName '#{source_image.file_name}' -ImageName '#{source_image.image_name}' -NewFileName '#{target_image.file_name}' -NewImageName '#{target_image.image_name}'") 'Copy-WdsInstallImage',
sh.run("Set-WdsInstallImage -ImageGroup '#{target_image.image_group}' -FileName '#{target_image.file_name}' -ImageName '#{target_image.image_name}' -DisplayOrder 99999 -UnattendFile '#{file_path}' -OverwriteUnattend") " -ImageGroup '#{source_image.image_group}'",
end " -FileName '#{source_image.file_name}'",
" -ImageName '#{source_image.image_name}'",
" -NewFileName '#{target_image.file_name}'",
" -NewImageName '#{target_image.image_name}'"
].map(&:strip).join(' ')
script << [
'Set-WdsInstallImage',
" -ImageGroup '#{target_image.image_group}'",
" -FileName '#{target_image.file_name}'",
" -ImageName '#{target_image.image_name}'",
' -DisplayOrder 99999',
" -UnattendFile '#{file_path}'",
' -OverwriteUnattend'
].map(&:strip).join(' ')
end end
run_pwsh script.join("\n"), json: false
end end
def delete_unattend(host) def delete_unattend(host)
image = target_image_for(host) image = target_image_for(host)
connection.shell(:powershell) do |sh| command = []
sh.run("Remove-WdsInstallImage -ImageGroup '#{image.image_group}' -ImageName '#{image.image_name}' -FileName '#{image.file_name}'") command << [
sh.run("Remove-Item -Path '#{unattend_file(host)}'") 'Remove-WdsInstallImage',
end.errcode.zero? " -ImageGroup '#{image.image_group}'",
" -ImageName '#{image.image_name}'",
" -FileName '#{image.file_name}'"
].map(&:strip).join(' ')
command << "Remove-Item -Path '#{unattend_file(host)}'"
run_pwsh(command.join("\n"), json: false).errcode.zero?
end end
def ensure_client(_host) def ensure_client(_host)
...@@ -242,14 +279,12 @@ class WdsServer < ApplicationRecord ...@@ -242,14 +279,12 @@ class WdsServer < ApplicationRecord
objects = nil if objects.empty? objects = nil if objects.empty?
unless objects unless objects
begin result = run_pwsh "Get-WDS#{type.to_s.capitalize}Image#{" -ImageName '#{name.sub("'", "`'")}'" if name}"
result = connection.shell(:powershell) do |s|
s.run("Get-WDS#{type.to_s.capitalize}Image #{"-ImageName '#{name.sub("'", "`'")}'" if name} | ConvertTo-Json -Compress")
end
begin
objects = underscore_result([JSON.parse(result.stdout)].flatten) objects = underscore_result([JSON.parse(result.stdout)].flatten)
rescue JSON::ParserError => e rescue JSON::ParserError => e
::Rails.logger.error "#{e.class}: #{e}\n#{result}" ::Rails.logger.error "Failed to parse images - #{e.class}: #{e}, the data was;\n#{result.inspect}"
raise e raise e
end end
end end
...@@ -270,6 +305,14 @@ class WdsServer < ApplicationRecord ...@@ -270,6 +305,14 @@ class WdsServer < ApplicationRecord
end end
end end
def run_pwsh(command, json: true)
command = [command] unless command.is_a? Array
command << '| ConvertTo-Json -Compress' if json
connection.shell(:powershell) do |s|
s.run command.join(' ')
end
end
def run_wql(wql, on_error: :raise) def run_wql(wql, on_error: :raise)
connection.run_wql(wql) connection.run_wql(wql)
rescue StandardError rescue StandardError
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
FactoryBot.define do FactoryBot.define do
factory :wds_facet, class: 'ForemanWds::WdsFacet' do factory :wds_facet, class: 'ForemanWds::WdsFacet' do
host host
wds_server
install_image_name { 'install.wim' }
end end
end end
# frozen_string_literal: true
require 'test_plugin_helper'
module ForemanWds
class WDSFacetTest < ActiveSupport::TestCase
let(:wds_server) do
FactoryBot.build(:wds_server)
end
let(:host) do
FactoryBot.build(:host, :managed, :with_wds_facet) do |host|
host.wds_facet.wds_server = wds_server
end
end
context 'without WDS server' do
let(:wds_server) { nil }
it 'does not error' do
assert_nil host.wds_facet.boot_image
assert_nil host.wds_facet.install_image
end
end
context 'with WDS server' do
setup do
wds_server.stubs(:run_wql).returns({})
wds_server.stubs(:run_pwsh).with('Get-WDSBootImage').returns(OpenStruct.new stdout: '[]')
wds_server.stubs(:run_pwsh).with("Get-WDSInstallImage -ImageName 'install.wim'").returns(OpenStruct.new stdout: '[]')
end
it 'does not error' do
assert_nil host.wds_facet.boot_image
assert_nil host.wds_facet.install_image
end
end
end
end
...@@ -21,3 +21,48 @@ ActiveSupport::TestCase.file_fixture_path = File.join(__dir__, 'fixtures') ...@@ -21,3 +21,48 @@ ActiveSupport::TestCase.file_fixture_path = File.join(__dir__, 'fixtures')
# Add plugin to FactoryBot's paths # Add plugin to FactoryBot's paths
FactoryBot.definition_file_paths << File.join(__dir__, 'factories') FactoryBot.definition_file_paths << File.join(__dir__, 'factories')
FactoryBot.reload FactoryBot.reload
class ActiveSupport::TestCase
setup :setup_winrm_stubs
def stub_winrm_powershell(command = nil, &block)
ret = if block_given?
@winrm_shell_mock[:powershell].stubs(:run).with(&block)
else
@winrm_shell_mock[:powershell].stubs(:run).with(command)
end
class << ret
def returns_pwsh(value, **params)
returns OpenStruct.new(stdout: value, **params)
end
end
ret
end
def stub_winrm_wql(query = nil)
if query
WinRM::Connection.any_instance.stubs(:run_wql).with(query)
else
WinRM::Connection.any_instance.stubs(:run_wql)
end
end
private
def setup_winrm_stubs
return if @winrm_mock
@winrm_mock = true
require 'winrm'
transport_mock = mock('winrm::http::transport')
WinRM::Connection.any_instance.stubs(:transport).returns(transport_mock)
transport_mock.stubs(:send_request).raises(StandardError, 'Real WinRM connections are not allowed')
@winrm_shell_mock = {
powershell: mock('winrm::shell::powershell')
}
WinRM::Connection.any_instance.stubs(:shell).with(:powershell).yields @winrm_shell_mock[:powershell]
end
end