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
objects = run_wql('SELECT * FROM MSFT_WdsClient', on_error: {})[:msft_wdsclient]
objects = nil if objects&.empty?
objects ||= begin
data = connection.shell(:powershell) do |s|
s.run('Get-WdsClient | ConvertTo-Json -Compress')
end.stdout
data = '[]' if data.empty?
underscore_result([JSON.parse(data)].flatten)
clients = run_pwsh('Get-WdsClient').stdout
clients = '[]' if clients.empty?
underscore_result([JSON.parse(clients)].flatten)
end
objects
end
def client(host)
device_ids = [host.mac.upcase.tr(':', '-'), host.name]
device_names = [host.name, host.shortname]
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
......@@ -82,18 +81,27 @@ class WdsServer < ApplicationRecord
raise NotImplementedError, 'Not finished yet'
ensure_unattend(host)
connection.shell(:powershell) do |sh|
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'")
end
run_pwsh [
'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
].join('\\').then { |path| "'#{path}'" },
"-PxePromptPolicy 'NoPrompt'"
].join(' ')
end
def delete_client(host)
raise NotImplementedError, 'Not finished yet'
delete_unattend(host)
connection.shell(:powershell) do |sh|
sh.run("Remove-WdsClient -DeviceID '#{host.mac.upcase.delete ':'}'")
end
run_pwsh("Remove-WdsClient -DeviceID '#{host.mac.upcase.delete ':'}'", json: false)
end
def all_images
......@@ -162,9 +170,17 @@ class WdsServer < ApplicationRecord
def unattend_path
cache.cache(:unattend_path) do
JSON.parse(connection.shell(:powershell) do |sh|
sh.run('Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\WDSServer\Providers\WDSTFTP -Name RootFolder | select RootFolder | ConvertTo-Json -Compress')
end, symbolize_names: true)[:RootFolder]
JSON.parse(
run_pwsh(
[
'Get-ItemProperty',
' -Path HKLM:\SYSTEM\CurrentControlSet\Services\WDSServer\Providers\WDSTFTP',
' -Name RootFolder',
'| select RootFolder'
].map(&:strip).join(' ')
),
symbolize_names: true
)[:RootFolder]
end
end
......@@ -187,6 +203,7 @@ class WdsServer < ApplicationRecord
raise 'No provisioning interface available' unless iface
raise NotImplementedException, 'TODO: Not implemented yet'
raise NotImplementedException, 'TODO: Not implemented yet' if SETTINGS[:wds_unattend_group]
# TODO: render template, send as heredoc
template = host.operatingsystem.provisioning_templates.find { |t| t.template_kind.name == 'wds_unattend' }
......@@ -198,33 +215,53 @@ class WdsServer < ApplicationRecord
template_data = host.render_template template: template
connection.shell(:powershell) do |sh|
file_path = unattend_file(host)
sh.run("$unattend_render = @'\n#{template_data}\n'@")
sh.run("New-Item -Path '#{file_path}' -ItemType 'file' -Value $unattend_render")
source_image = host.wds_facet.install_image
target_image = target_image_for(host)
if SETTINGS[:wds_unattend_group]
raise NotImplementedException, 'TODO: Not implemented yet'
# New-WdsInstallImageGroup -Name #{SETTINGS[:wds_unattend_group]}
# Export-WdsInstallImage -ImageGroup <Group> ...
# Import-WdsInstallImage -ImageGroup #{SETTINGS[:wds_unattend_group]} -UnattendFile '#{file_path}' -OverwriteUnattend ...
else
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}'")
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")
end
file_path = unattend_file(host)
script = []
script << "$unattend_render = @'\n#{template_data}\n'@"
script << "New-Item -Path '#{file_path}' -ItemType 'file' -Value $unattend_render"
source_image = host.wds_facet.install_image
target_image = target_image_for(host)
if SETTINGS[:wds_unattend_group]
# New-WdsInstallImageGroup -Name #{SETTINGS[:wds_unattend_group]}
# Export-WdsInstallImage -ImageGroup <Group> ...
# Import-WdsInstallImage -ImageGroup #{SETTINGS[:wds_unattend_group]} -UnattendFile '#{file_path}' -OverwriteUnattend ...
else
script << [
'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}'"
].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
run_pwsh script.join("\n"), json: false
end
def delete_unattend(host)
image = target_image_for(host)
connection.shell(:powershell) do |sh|
sh.run("Remove-WdsInstallImage -ImageGroup '#{image.image_group}' -ImageName '#{image.image_name}' -FileName '#{image.file_name}'")
sh.run("Remove-Item -Path '#{unattend_file(host)}'")
end.errcode.zero?
command = []
command << [
'Remove-WdsInstallImage',
" -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
def ensure_client(_host)
......@@ -242,14 +279,12 @@ class WdsServer < ApplicationRecord
objects = nil if objects.empty?
unless objects
begin
result = connection.shell(:powershell) do |s|
s.run("Get-WDS#{type.to_s.capitalize}Image #{"-ImageName '#{name.sub("'", "`'")}'" if name} | ConvertTo-Json -Compress")
end
result = run_pwsh "Get-WDS#{type.to_s.capitalize}Image#{" -ImageName '#{name.sub("'", "`'")}'" if name}"
begin
objects = underscore_result([JSON.parse(result.stdout)].flatten)
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
end
end
......@@ -270,6 +305,14 @@ class WdsServer < ApplicationRecord
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)
connection.run_wql(wql)
rescue StandardError
......
......@@ -3,6 +3,7 @@
FactoryBot.define do
factory :wds_facet, class: 'ForemanWds::WdsFacet' do
host
wds_server
install_image_name { 'install.wim' }
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')
# Add plugin to FactoryBot's paths
FactoryBot.definition_file_paths << File.join(__dir__, 'factories')
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