From 045e06455a1c26d7a91841d158ca9736e47a7936 Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Thu, 24 Apr 2014 07:39:58 -0700 Subject: [PATCH 1/9] Added WinRM elevated shell wrapper script This script creates an immediately run scheduled task using fresh credentials. This is a generic implementation used by the Chef provisioners. The script gets around several limitations in WinRM. 1. Credential hopping 2. The non-default Administrator account sometimes doesn't have true Administrator access when run through WinRM even with UAC disabled. In short, this script allows commands to run through WinRM just as if they were run directly on the box. --- .../winrm/scripts/elevated_shell.ps1.erb | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 plugins/communicators/winrm/scripts/elevated_shell.ps1.erb diff --git a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb new file mode 100644 index 000000000..8e48a219e --- /dev/null +++ b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb @@ -0,0 +1,95 @@ +$command = "<%= options[:command] %>" +$user = "<%= options[:username] %>" +$password = "<%= options[:password] %>" + +$task_name = "WinRM_Elevated_Shell" +$out_file = "$env:SystemRoot\Temp\WinRM_Elevated_Shell.log" + +if (Test-Path $out_file) { + del $out_file +} + +$task_xml = @' + + + + + {user} + Password + HighestAvailable + + + + IgnoreNew + false + false + true + false + false + + true + false + + true + true + false + false + false + PT2H + 4 + + + + cmd + {arguments} + + + +'@ + +$bytes = [System.Text.Encoding]::Unicode.GetBytes($command) +$encoded_command = [Convert]::ToBase64String($bytes) +$arguments = "/c powershell.exe -EncodedCommand $encoded_command > $out_file 2>&1" + +$task_xml = $task_xml.Replace("{arguments}", $arguments) +$task_xml = $task_xml.Replace("{user}", $user) + +$schedule = New-Object -ComObject "Schedule.Service" +$schedule.Connect() +$task = $schedule.NewTask($null) +$task.XmlText = $task_xml +$folder = $schedule.GetFolder("\") +$folder.RegisterTaskDefinition($task_name, $task, 6, $user, $password, 1, $null) | Out-Null + +$registered_task = $folder.GetTask("\$task_name") +$registered_task.Run($null) | Out-Null + +$timeout = 10 +$sec = 0 +while ( (!($registered_task.state -eq 4)) -and ($sec -lt $timeout) ) { + Start-Sleep -s 1 + $sec++ +} + +# Read the entire file, but only write out new lines we haven't seen before +$numLinesRead = 0 +do { + Start-Sleep -m 100 + + if (Test-Path $out_file) { + $text = (get-content $out_file) + $numLines = ($text | Measure-Object -line).lines + $numLinesToRead = $numLines - $numLinesRead + + if ($numLinesToRead -gt 0) { + $text | select -first $numLinesToRead -skip $numLinesRead | ForEach { + Write-Host "$_" + } + $numLinesRead += $numLinesToRead + } + } +} while (!($registered_task.state -eq 3)) + +$exit_code = $registered_task.LastTaskResult +[System.Runtime.Interopservices.Marshal]::ReleaseComObject($schedule) | Out-Null +exit $exit_code From 1dd081d8665134bcb9636d2b49efce0fded4a08c Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Thu, 24 Apr 2014 08:03:05 -0700 Subject: [PATCH 2/9] Don't use interpolated strings for username and password Its possible that usernames and passwords may contain special characters like $ --- plugins/communicators/winrm/scripts/elevated_shell.ps1.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb index 8e48a219e..97c5f822a 100644 --- a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb +++ b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb @@ -1,6 +1,6 @@ $command = "<%= options[:command] %>" -$user = "<%= options[:username] %>" -$password = "<%= options[:password] %>" +$user = '<%= options[:username] %>' +$password = '<%= options[:password] %>' $task_name = "WinRM_Elevated_Shell" $out_file = "$env:SystemRoot\Temp\WinRM_Elevated_Shell.log" From f18a3972899466e2a66aab9a23a622ef4edbc225 Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Thu, 24 Apr 2014 08:24:12 -0700 Subject: [PATCH 3/9] Allow WinRM commands to be run elevated via scheduled task --- plugins/communicators/winrm/communicator.rb | 12 +++++++++++- .../communicators/winrm/communicator_test.rb | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/plugins/communicators/winrm/communicator.rb b/plugins/communicators/winrm/communicator.rb index c9cfa8327..670748536 100644 --- a/plugins/communicators/winrm/communicator.rb +++ b/plugins/communicators/winrm/communicator.rb @@ -61,9 +61,19 @@ module VagrantPlugins :error_class => Errors::ExecutionError, :error_key => :execution_error, :command => command, - :shell => :powershell + :shell => :powershell, + :elevated => false }.merge(opts || {}) + if opts[:elevated] + path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__) + command = Vagrant::Util::TemplateRenderer.render(path, options: { + username: shell.username, + password: shell.password, + command: command, + }) + end + output = shell.send(opts[:shell], command, &block) execution_output(output, opts) end diff --git a/test/unit/plugins/communicators/winrm/communicator_test.rb b/test/unit/plugins/communicators/winrm/communicator_test.rb index 84a4e8bd2..d6013be7c 100644 --- a/test/unit/plugins/communicators/winrm/communicator_test.rb +++ b/test/unit/plugins/communicators/winrm/communicator_test.rb @@ -17,6 +17,11 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do end end + before do + allow(shell).to receive(:username).and_return('vagrant') + allow(shell).to receive(:password).and_return('password') + end + describe ".ready?" do it "returns true if hostname command executes without error" do expect(shell).to receive(:powershell).with("hostname").and_return({ exitcode: 0 }) @@ -42,6 +47,16 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do expect(subject.execute("dir")).to eq(0) end + it "wraps command in elevated shell script when elevated is true" do + expect(shell).to receive(:powershell) do |cmd| + expect(cmd).to include("$command = \"dir\"") + expect(cmd).to include("$user = 'vagrant'") + expect(cmd).to include("$password = 'password'") + expect(cmd).to include("New-Object -ComObject \"Schedule.Service\"") + end.and_return({ exitcode: 0 }) + expect(subject.execute("dir", { elevated: true })).to eq(0) + end + it "can use cmd shell" do expect(shell).to receive(:cmd).with(kind_of(String)).and_return({ exitcode: 0 }) expect(subject.execute("dir", { :shell => :cmd })).to eq(0) From 30b0399431ba533b30037c84d18036fc242fef85 Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Thu, 24 Apr 2014 08:26:38 -0700 Subject: [PATCH 4/9] Use new Ruby hash initializer syntax --- plugins/communicators/winrm/communicator.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/communicators/winrm/communicator.rb b/plugins/communicators/winrm/communicator.rb index 670748536..3bed39f72 100644 --- a/plugins/communicators/winrm/communicator.rb +++ b/plugins/communicators/winrm/communicator.rb @@ -57,12 +57,12 @@ module VagrantPlugins return 0 if command.empty? opts = { - :error_check => true, - :error_class => Errors::ExecutionError, - :error_key => :execution_error, - :command => command, - :shell => :powershell, - :elevated => false + error_check: true, + error_class: Errors::ExecutionError, + error_key: :execution_error, + command: command, + shell: :powershell, + elevated: false }.merge(opts || {}) if opts[:elevated] @@ -131,7 +131,7 @@ module VagrantPlugins # The error classes expect the translation key to be _key, but that makes for an ugly # configuration parameter, so we set it here from `error_key` msg = "Command execution failed with an exit code of #{output[:exitcode]}" - error_opts = opts.merge(:_key => opts[:error_key], :message => msg) + error_opts = opts.merge(_key: opts[:error_key], message: msg) raise opts[:error_class], error_opts end end #WinRM class From 99ce95198bcb4323a686d9782f6ba6b2fb4cb7e8 Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Fri, 25 Apr 2014 10:17:00 -0700 Subject: [PATCH 5/9] First pass at integrating new WinRM elevated execution wrapper - Removed a lot of the Windows special casing from Chef runs - Don't attempt to use @config.binary_env since we don't properly pass this along to Chef --- .../chef/command_builder_windows.rb | 84 ++++--------------- .../chef/provisioner/chef_client.rb | 3 +- .../chef/provisioner/chef_solo.rb | 3 +- 3 files changed, 21 insertions(+), 69 deletions(-) diff --git a/plugins/provisioners/chef/command_builder_windows.rb b/plugins/provisioners/chef/command_builder_windows.rb index 8bd4f972e..f8add1931 100644 --- a/plugins/provisioners/chef/command_builder_windows.rb +++ b/plugins/provisioners/chef/command_builder_windows.rb @@ -1,82 +1,32 @@ -require "tempfile" - -require "vagrant/util/template_renderer" - module VagrantPlugins module Chef class CommandBuilderWindows < CommandBuilder def build_command - binary_path = "chef-#{@client_type}" - if @config.binary_path - binary_path = File.join(@config.binary_path, binary_path) - binary_path.gsub!("/", "\\") - binary_path = "c:#{binary_path}" if binary_path.start_with?("\\") - end - - chef_arguments = "-c #{provisioning_path("#{@client_type}.rb")}" - chef_arguments << " -j #{provisioning_path("dna.json")}" - chef_arguments << " #{@config.arguments}" if @config.arguments - - command_env = "" - command_env = "#{@config.binary_env} " if @config.binary_env - - task_ps1_path = provisioning_path("cheftask.ps1") - - opts = { - user: @machine.config.winrm.username, - pass: @machine.config.winrm.password, - chef_arguments: chef_arguments, - chef_binary_path: "#{command_env}#{binary_path}", - chef_stdout_log: provisioning_path("chef-#{@client_type}.log"), - chef_stderr_log: provisioning_path("chef-#{@client_type}.err.log"), - chef_task_exitcode: provisioning_path('cheftask.exitcode'), - chef_task_running: provisioning_path('cheftask.running'), - chef_task_ps1: task_ps1_path, - chef_task_run_ps1: provisioning_path('cheftaskrun.ps1'), - chef_task_xml: provisioning_path('cheftask.xml'), - } - - # Upload the files we'll need - render_and_upload( - "cheftaskrun.ps1", opts[:chef_task_run_ps1], opts) - render_and_upload( - "cheftask.xml", opts[:chef_task_xml], opts) - render_and_upload( - "cheftask.ps1", opts[:chef_task_ps1], opts) - - return <<-EOH - $old = Get-ExecutionPolicy; - Set-ExecutionPolicy Unrestricted -force; - #{task_ps1_path}; - Set-ExecutionPolicy $old -force - EOH + "#{chef_binary_path} #{chef_arguments}" end protected - def provisioning_path(file) - path = "#{@config.provisioning_path}/#{file}" - path.gsub!("/", "\\") - path = "c:#{path}" if path.start_with?("\\") - path + def chef_binary_path + binary_path = "chef-#{@client_type}" + binary_path = win_path(File.join(@config.binary_path, binary_path)) if @config.binary_path + binary_path end - def render_and_upload(template, dest, opts) - path = File.expand_path("../scripts/#{template}", __FILE__) - data = Vagrant::Util::TemplateRenderer.render(path, options) + def chef_arguments + chef_arguments = "-c #{provisioning_path("#{@client_type}.rb")}" + chef_arguments << " -j #{provisioning_path("dna.json")}" + chef_arguments << " #{@config.arguments}" if @config.arguments + chef_arguments.strip + end - file = Tempfile.new("vagrant-chef") - file.binmode - file.write(data) - file.fsync - file.close + def provisioning_path(file) + win_path(File.join(@config.provisioning_path, file)) + end - @machine.communicate.upload(file.path, dest) - ensure - if file - file.close - file.unlink - end + def win_path(path) + path.gsub!("/", "\\") + "c:#{path}" if path.start_with?("\\") end end end diff --git a/plugins/provisioners/chef/provisioner/chef_client.rb b/plugins/provisioners/chef/provisioner/chef_client.rb index 1de5f5d16..50bf04c37 100644 --- a/plugins/provisioners/chef/provisioner/chef_client.rb +++ b/plugins/provisioners/chef/provisioner/chef_client.rb @@ -75,7 +75,8 @@ module VagrantPlugins @machine.ui.info I18n.t("vagrant.provisioners.chef.running_client_again") end - exit_status = @machine.communicate.sudo(command, :error_check => false) do |type, data| + opts = { error_check: false, elevated: true } + exit_status = @machine.communicate.sudo(command, opts) do |type, data| # Output the data with the proper color based on the stream. color = type == :stdout ? :green : :red diff --git a/plugins/provisioners/chef/provisioner/chef_solo.rb b/plugins/provisioners/chef/provisioner/chef_solo.rb index 99037c1f0..38e5d56e0 100644 --- a/plugins/provisioners/chef/provisioner/chef_solo.rb +++ b/plugins/provisioners/chef/provisioner/chef_solo.rb @@ -151,7 +151,8 @@ module VagrantPlugins @machine.ui.info I18n.t("vagrant.provisioners.chef.running_solo_again") end - exit_status = @machine.communicate.sudo(command, :error_check => false) do |type, data| + opts = { error_check: false, elevated: true } + exit_status = @machine.communicate.sudo(command, opts) do |type, data| # Output the data with the proper color based on the stream. color = type == :stdout ? :green : :red From 38c7203f92e6ffba4cb4873a73bc6d20050d9100 Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Fri, 25 Apr 2014 15:12:25 -0700 Subject: [PATCH 6/9] Chef provisioner should utilize reboot capability --- plugins/provisioners/chef/provisioner/chef_client.rb | 4 ++++ plugins/provisioners/chef/provisioner/chef_solo.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/plugins/provisioners/chef/provisioner/chef_client.rb b/plugins/provisioners/chef/provisioner/chef_client.rb index 50bf04c37..935cec6bf 100644 --- a/plugins/provisioners/chef/provisioner/chef_client.rb +++ b/plugins/provisioners/chef/provisioner/chef_client.rb @@ -58,6 +58,10 @@ module VagrantPlugins @machine.ui.warn(I18n.t("vagrant.chef_run_list_empty")) end + if @machine.guest.capability?(:wait_for_reboot) + @machine.guest.capability(:wait_for_reboot) + end + if windows? # This re-establishes our symbolic links if they were # created between now and a reboot diff --git a/plugins/provisioners/chef/provisioner/chef_solo.rb b/plugins/provisioners/chef/provisioner/chef_solo.rb index 38e5d56e0..db926e11c 100644 --- a/plugins/provisioners/chef/provisioner/chef_solo.rb +++ b/plugins/provisioners/chef/provisioner/chef_solo.rb @@ -134,6 +134,10 @@ module VagrantPlugins @machine.ui.warn(I18n.t("vagrant.chef_run_list_empty")) end + if @machine.guest.capability?(:wait_for_reboot) + @machine.guest.capability(:wait_for_reboot) + end + if windows? # This re-establishes our symbolic links if they were # created between now and a reboot From 68ce06561af0776c6fe060a5a52d77266fa0ba8b Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Fri, 25 Apr 2014 15:38:14 -0700 Subject: [PATCH 7/9] Removed old unused WinRM chef provisioner scripts --- .../chef/scripts/cheftask.ps1.erb | 48 ------------------- .../chef/scripts/cheftask.xml.erb | 45 ----------------- .../chef/scripts/cheftaskrun.ps1.erb | 18 ------- 3 files changed, 111 deletions(-) delete mode 100644 plugins/provisioners/chef/scripts/cheftask.ps1.erb delete mode 100644 plugins/provisioners/chef/scripts/cheftask.xml.erb delete mode 100644 plugins/provisioners/chef/scripts/cheftaskrun.ps1.erb diff --git a/plugins/provisioners/chef/scripts/cheftask.ps1.erb b/plugins/provisioners/chef/scripts/cheftask.ps1.erb deleted file mode 100644 index f025425ff..000000000 --- a/plugins/provisioners/chef/scripts/cheftask.ps1.erb +++ /dev/null @@ -1,48 +0,0 @@ -# kill the task so we can recreate it -schtasks /delete /tn "chef-solo" /f 2>&1 | out-null - -# Ensure the chef task running file doesn't exist from a previous failure -if (Test-Path "<%= options[:chef_task_running] %>") { - del "<%= options[:chef_task_running] %>" -} - -# schedule the task to run once in the far distant future -schtasks /create /tn 'chef-solo' /xml '<%= options[:chef_task_xml] %>' /ru '<%= options[:user] %>' /rp '<%= options[:pass] %>' | Out-Null - -# start the scheduled task right now -schtasks /run /tn "chef-solo" | Out-Null - -# wait for run_chef.ps1 to start or timeout after 1 minute -$timeoutSeconds = 60 -$elapsedSeconds = 0 -while ( (!(Test-Path "<%= options[:chef_task_running] %>")) -and ($elapsedSeconds -lt $timeoutSeconds) ) { - Start-Sleep -s 1 - $elapsedSeconds++ -} - -if ($elapsedSeconds -ge $timeoutSeconds) { - Write-Error "Timed out waiting for chef scheduled task to start" - exit -2 -} - -# read the entire file, but only write out new lines we haven't seen before -$numLinesRead = 0 -$success = $TRUE -while (Test-Path "<%= options[:chef_task_running] %>") { - Start-Sleep -m 100 - - if (Test-Path "<%= options[:chef_stdout_log] %>") { - $text = (get-content "<%= options[:chef_stdout_log] %>") - $numLines = ($text | Measure-Object -line).lines - $numLinesToRead = $numLines - $numLinesRead - - if ($numLinesToRead -gt 0) { - $text | select -first $numLinesToRead -skip $numLinesRead | ForEach { - Write-Host "$_" - } - $numLinesRead += $numLinesToRead - } - } -} - -exit Get-Content "<%= options[:chef_task_exitcode] %>" diff --git a/plugins/provisioners/chef/scripts/cheftask.xml.erb b/plugins/provisioners/chef/scripts/cheftask.xml.erb deleted file mode 100644 index 6b598ec4c..000000000 --- a/plugins/provisioners/chef/scripts/cheftask.xml.erb +++ /dev/null @@ -1,45 +0,0 @@ - - - - 2013-06-21T22:41:43 - Administrator - - - - 2045-01-01T12:00:00 - true - - - - - vagrant - Password - HighestAvailable - - - - IgnoreNew - false - false - true - false - false - - true - false - - true - true - false - false - false - PT2H - 4 - - - - powershell - -file <%= options[:chef_task_run_ps1] %> - - - diff --git a/plugins/provisioners/chef/scripts/cheftaskrun.ps1.erb b/plugins/provisioners/chef/scripts/cheftaskrun.ps1.erb deleted file mode 100644 index 0d4a43bf4..000000000 --- a/plugins/provisioners/chef/scripts/cheftaskrun.ps1.erb +++ /dev/null @@ -1,18 +0,0 @@ -$exitCode = -1 -Set-ExecutionPolicy Unrestricted -force; - -Try -{ - "running" | Out-File "<%= options[:chef_task_running] %>" - $process = (Start-Process "<%= options[:chef_binary_path] %>" -ArgumentList "<%= options[:chef_arguments] %>" -NoNewWindow -PassThru -Wait -RedirectStandardOutput "<%= options[:chef_stdout_log] %>" -RedirectStandardError "<%= options[:chef_stderr_log] %>") - $exitCode = $process.ExitCode -} -Finally -{ - $exitCode | Out-File "<%= options[:chef_task_exitcode] %>" - if (Test-Path "<%= options[:chef_task_running] %>") { - del "<%= options[:chef_task_running] %>" - } -} - -exit $exitCode From ebca0e7e44d54fb35d442871e57582299424fcbb Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Fri, 25 Apr 2014 18:56:54 -0700 Subject: [PATCH 8/9] Fixed bug in CommandBuilderWindows CommandBuilderWindows would not include the Chef binary in the command when the binary_path was specified in the config. Backfilled unit tests for CommandBuilderWindows --- .../chef/command_builder_windows.rb | 3 +- .../chef/command_build_windows_spec.rb | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 test/unit/plugins/provisioners/chef/command_build_windows_spec.rb diff --git a/plugins/provisioners/chef/command_builder_windows.rb b/plugins/provisioners/chef/command_builder_windows.rb index f8add1931..4c5439d62 100644 --- a/plugins/provisioners/chef/command_builder_windows.rb +++ b/plugins/provisioners/chef/command_builder_windows.rb @@ -26,7 +26,8 @@ module VagrantPlugins def win_path(path) path.gsub!("/", "\\") - "c:#{path}" if path.start_with?("\\") + path = "c:#{path}" if path.start_with?("\\") + path end end end diff --git a/test/unit/plugins/provisioners/chef/command_build_windows_spec.rb b/test/unit/plugins/provisioners/chef/command_build_windows_spec.rb new file mode 100644 index 000000000..5b8d4f325 --- /dev/null +++ b/test/unit/plugins/provisioners/chef/command_build_windows_spec.rb @@ -0,0 +1,55 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/provisioners/chef/command_builder_windows") + +describe VagrantPlugins::Chef::CommandBuilderWindows do + + let(:machine) { double("machine") } + let(:chef_config) { double("chef_config") } + + subject do + VagrantPlugins::Chef::CommandBuilderWindows.new(machine, chef_config, :client) + end + + before(:each) do + allow(chef_config).to receive(:provisioning_path).and_return('/tmp/vagrant-chef-1') + allow(chef_config).to receive(:arguments).and_return(nil) + allow(chef_config).to receive(:binary_env).and_return(nil) + allow(chef_config).to receive(:binary_path).and_return(nil) + end + + describe '.initialize' do + it 'should raise when chef type is not client or solo' do + expect { VagrantPlugins::Chef::CommandBuilderWindows.new(machine, chef_config, :client_bad) }. + to raise_error + end + end + + describe '.build_command' do + it "executes the chef-client in PATH by default" do + expect(subject.build_command()).to match(/^chef-client/) + end + + it "executes the chef-client using full path if binary_path is specified" do + allow(chef_config).to receive(:binary_path).and_return( + "c:\\opscode\\chef\\bin\\chef-client") + expect(subject.build_command()).to match(/^c:\\opscode\\chef\\bin\\chef-client\\chef-client/) + end + + it "builds a windows friendly client.rb path" do + expect(subject.build_command()).to include( + '-c c:\\tmp\\vagrant-chef-1\\client.rb') + end + + it "builds a windows friendly solo.json path" do + expect(subject.build_command()).to include( + '-j c:\\tmp\\vagrant-chef-1\\dna.json') + end + + it 'includes Chef arguments if specified' do + allow(chef_config).to receive(:arguments).and_return("-l DEBUG") + expect(subject.build_command()).to include( + '-l DEBUG') + end + end +end From cf716348139bedabf4ebf56f588e2481dbe110c8 Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Fri, 25 Apr 2014 19:51:18 -0700 Subject: [PATCH 9/9] DRY'd up Chef command building There's very little difference between the command building on Linux and Windows other than path formatting. All Chef provisioners support the --no-color argument now. Added unit tests to verify changes. --- plugins/provisioners/chef/command_builder.rb | 46 +++++++- .../chef/command_builder_linux.rb | 47 -------- .../chef/command_builder_windows.rb | 34 ------ plugins/provisioners/chef/plugin.rb | 2 - plugins/provisioners/chef/provisioner/base.rb | 4 +- .../chef/command_build_windows_spec.rb | 55 --------- .../provisioners/chef/command_builder_spec.rb | 105 ++++++++++++++++++ 7 files changed, 148 insertions(+), 145 deletions(-) delete mode 100644 plugins/provisioners/chef/command_builder_linux.rb delete mode 100644 plugins/provisioners/chef/command_builder_windows.rb delete mode 100644 test/unit/plugins/provisioners/chef/command_build_windows_spec.rb create mode 100644 test/unit/plugins/provisioners/chef/command_builder_spec.rb diff --git a/plugins/provisioners/chef/command_builder.rb b/plugins/provisioners/chef/command_builder.rb index 8a1e26b82..844d48864 100644 --- a/plugins/provisioners/chef/command_builder.rb +++ b/plugins/provisioners/chef/command_builder.rb @@ -1,15 +1,53 @@ module VagrantPlugins module Chef class CommandBuilder - def initialize(machine, config, client_type) - @machine = machine - @config = config - @client_type = client_type + def initialize(config, client_type, is_windows=false, is_ui_colored=false) + @client_type = client_type + @config = config + @is_windows = is_windows + @is_ui_colored = is_ui_colored if client_type != :solo && client_type != :client raise 'Invalid client_type, expected solo or client' end end + + def build_command + "#{command_env}#{chef_binary_path} #{chef_arguments}" + end + + protected + + def command_env + @config.binary_env ? "#{@config.binary_env} " : "" + end + + def chef_binary_path + binary_path = "chef-#{@client_type}" + if @config.binary_path + binary_path = guest_friendly_path(File.join(@config.binary_path, binary_path)) + end + binary_path + end + + def chef_arguments + chef_arguments = "-c #{provisioning_path("#{@client_type}.rb")}" + chef_arguments << " -j #{provisioning_path("dna.json")}" + chef_arguments << " #{@config.arguments}" if @config.arguments + chef_arguments << " --no-color" unless @is_ui_colored + chef_arguments.strip + end + + def provisioning_path(file) + guest_friendly_path(File.join(@config.provisioning_path, file)) + end + + def guest_friendly_path(path) + return path unless @is_windows + path.gsub!("/", "\\") + path = "c:#{path}" if path.start_with?("\\") + path + end end end end diff --git a/plugins/provisioners/chef/command_builder_linux.rb b/plugins/provisioners/chef/command_builder_linux.rb deleted file mode 100644 index d33ed667a..000000000 --- a/plugins/provisioners/chef/command_builder_linux.rb +++ /dev/null @@ -1,47 +0,0 @@ -module VagrantPlugins - module Chef - class CommandBuilderLinux < CommandBuilder - def build_command - if @client_type == :solo - return build_command_solo - else - return build_command_client - end - end - - protected - - def build_command_client - command_env = @config.binary_env ? "#{@config.binary_env} " : "" - command_args = @config.arguments ? " #{@config.arguments}" : "" - - binary_path = "chef-client" - binary_path ||= File.join(@config.binary_path, binary_path) - - return "#{command_env}#{binary_path} " + - "-c #{@config.provisioning_path}/client.rb " + - "-j #{@config.provisioning_path}/dna.json #{command_args}" - end - - def build_command_solo - options = [ - "-c #{@config.provisioning_path}/solo.rb", - "-j #{@config.provisioning_path}/dna.json" - ] - - if !@machine.env.ui.is_a?(Vagrant::UI::Colored) - options << "--no-color" - end - - command_env = @config.binary_env ? "#{@config.binary_env} " : "" - command_args = @config.arguments ? " #{@config.arguments}" : "" - - binary_path = "chef-solo" - binary_path ||= File.join(@config.binary_path, binary_path) - - return "#{command_env}#{binary_path} " + - "#{options.join(" ")} #{command_args}" - end - end - end -end diff --git a/plugins/provisioners/chef/command_builder_windows.rb b/plugins/provisioners/chef/command_builder_windows.rb deleted file mode 100644 index 4c5439d62..000000000 --- a/plugins/provisioners/chef/command_builder_windows.rb +++ /dev/null @@ -1,34 +0,0 @@ -module VagrantPlugins - module Chef - class CommandBuilderWindows < CommandBuilder - def build_command - "#{chef_binary_path} #{chef_arguments}" - end - - protected - - def chef_binary_path - binary_path = "chef-#{@client_type}" - binary_path = win_path(File.join(@config.binary_path, binary_path)) if @config.binary_path - binary_path - end - - def chef_arguments - chef_arguments = "-c #{provisioning_path("#{@client_type}.rb")}" - chef_arguments << " -j #{provisioning_path("dna.json")}" - chef_arguments << " #{@config.arguments}" if @config.arguments - chef_arguments.strip - end - - def provisioning_path(file) - win_path(File.join(@config.provisioning_path, file)) - end - - def win_path(path) - path.gsub!("/", "\\") - path = "c:#{path}" if path.start_with?("\\") - path - end - end - end -end diff --git a/plugins/provisioners/chef/plugin.rb b/plugins/provisioners/chef/plugin.rb index 468f4b242..afe068047 100644 --- a/plugins/provisioners/chef/plugin.rb +++ b/plugins/provisioners/chef/plugin.rb @@ -6,8 +6,6 @@ module VagrantPlugins module Chef root = Pathname.new(File.expand_path("../", __FILE__)) autoload :CommandBuilder, root.join("command_builder") - autoload :CommandBuilderLinux, root.join("command_builder_linux") - autoload :CommandBuilderWindows, root.join("command_builder_windows") class Plugin < Vagrant.plugin("2") name "chef" diff --git a/plugins/provisioners/chef/provisioner/base.rb b/plugins/provisioners/chef/provisioner/base.rb index b1c071a6a..02a4dd52f 100644 --- a/plugins/provisioners/chef/provisioner/base.rb +++ b/plugins/provisioners/chef/provisioner/base.rb @@ -26,9 +26,7 @@ module VagrantPlugins # This returns the command to run Chef for the given client # type. def build_command(client) - builder_klass = CommandBuilderLinux - builder_klass = CommandBuilderWindows if windows? - builder = builder_klass.new(@machine, @config, client) + builder = CommandBuilder.new(@config, client, windows?, @machine.env.ui.is_a?(Vagrant::UI::Colored)) return builder.build_command end diff --git a/test/unit/plugins/provisioners/chef/command_build_windows_spec.rb b/test/unit/plugins/provisioners/chef/command_build_windows_spec.rb deleted file mode 100644 index 5b8d4f325..000000000 --- a/test/unit/plugins/provisioners/chef/command_build_windows_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require_relative "../../../base" - -require Vagrant.source_root.join("plugins/provisioners/chef/command_builder_windows") - -describe VagrantPlugins::Chef::CommandBuilderWindows do - - let(:machine) { double("machine") } - let(:chef_config) { double("chef_config") } - - subject do - VagrantPlugins::Chef::CommandBuilderWindows.new(machine, chef_config, :client) - end - - before(:each) do - allow(chef_config).to receive(:provisioning_path).and_return('/tmp/vagrant-chef-1') - allow(chef_config).to receive(:arguments).and_return(nil) - allow(chef_config).to receive(:binary_env).and_return(nil) - allow(chef_config).to receive(:binary_path).and_return(nil) - end - - describe '.initialize' do - it 'should raise when chef type is not client or solo' do - expect { VagrantPlugins::Chef::CommandBuilderWindows.new(machine, chef_config, :client_bad) }. - to raise_error - end - end - - describe '.build_command' do - it "executes the chef-client in PATH by default" do - expect(subject.build_command()).to match(/^chef-client/) - end - - it "executes the chef-client using full path if binary_path is specified" do - allow(chef_config).to receive(:binary_path).and_return( - "c:\\opscode\\chef\\bin\\chef-client") - expect(subject.build_command()).to match(/^c:\\opscode\\chef\\bin\\chef-client\\chef-client/) - end - - it "builds a windows friendly client.rb path" do - expect(subject.build_command()).to include( - '-c c:\\tmp\\vagrant-chef-1\\client.rb') - end - - it "builds a windows friendly solo.json path" do - expect(subject.build_command()).to include( - '-j c:\\tmp\\vagrant-chef-1\\dna.json') - end - - it 'includes Chef arguments if specified' do - allow(chef_config).to receive(:arguments).and_return("-l DEBUG") - expect(subject.build_command()).to include( - '-l DEBUG') - end - end -end diff --git a/test/unit/plugins/provisioners/chef/command_builder_spec.rb b/test/unit/plugins/provisioners/chef/command_builder_spec.rb new file mode 100644 index 000000000..3fb53693b --- /dev/null +++ b/test/unit/plugins/provisioners/chef/command_builder_spec.rb @@ -0,0 +1,105 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/provisioners/chef/command_builder") + +describe VagrantPlugins::Chef::CommandBuilder do + + let(:machine) { double("machine") } + let(:chef_config) { double("chef_config") } + + before(:each) do + allow(chef_config).to receive(:provisioning_path).and_return('/tmp/vagrant-chef-1') + allow(chef_config).to receive(:arguments).and_return(nil) + allow(chef_config).to receive(:binary_env).and_return(nil) + allow(chef_config).to receive(:binary_path).and_return(nil) + allow(chef_config).to receive(:binary_env).and_return(nil) + end + + describe '.initialize' do + it 'should raise when chef type is not client or solo' do + expect { VagrantPlugins::Chef::CommandBuilder.new(chef_config, :client_bad) }. + to raise_error + end + end + + describe 'build_command' do + describe 'windows' do + subject do + VagrantPlugins::Chef::CommandBuilder.new(chef_config, :client, true) + end + + it "executes the chef-client in PATH by default" do + expect(subject.build_command()).to match(/^chef-client/) + end + + it "executes the chef-client using full path if binary_path is specified" do + allow(chef_config).to receive(:binary_path).and_return( + "c:\\opscode\\chef\\bin\\chef-client") + expect(subject.build_command()).to match(/^c:\\opscode\\chef\\bin\\chef-client\\chef-client/) + end + + it "builds a guest friendly client.rb path" do + expect(subject.build_command()).to include( + '-c c:\\tmp\\vagrant-chef-1\\client.rb') + end + + it "builds a guest friendly solo.json path" do + expect(subject.build_command()).to include( + '-j c:\\tmp\\vagrant-chef-1\\dna.json') + end + + it 'includes Chef arguments if specified' do + allow(chef_config).to receive(:arguments).and_return("-l DEBUG") + expect(subject.build_command()).to include( + '-l DEBUG') + end + + it 'includes --no-color if UI is not colored' do + expect(subject.build_command()).to include( + ' --no-color') + end + end + + describe 'linux' do + subject do + VagrantPlugins::Chef::CommandBuilder.new(chef_config, :client, false) + end + + it "executes the chef-client in PATH by default" do + expect(subject.build_command()).to match(/^chef-client/) + end + + it "executes the chef-client using full path if binary_path is specified" do + allow(chef_config).to receive(:binary_path).and_return( + "/opt/chef/chef-client") + expect(subject.build_command()).to match(/^\/opt\/chef\/chef-client/) + end + + it "builds a guest friendly client.rb path" do + expect(subject.build_command()).to include( + '-c /tmp/vagrant-chef-1/client.rb') + end + + it "builds a guest friendly solo.json path" do + expect(subject.build_command()).to include( + '-j /tmp/vagrant-chef-1/dna.json') + end + + it 'includes Chef arguments if specified' do + allow(chef_config).to receive(:arguments).and_return("-l DEBUG") + expect(subject.build_command()).to include( + '-l DEBUG') + end + + it 'includes --no-color if UI is not colored' do + expect(subject.build_command()).to include( + ' --no-color') + end + + it 'includes environment variables if specified' do + allow(chef_config).to receive(:binary_env).and_return("ENVVAR=VAL") + expect(subject.build_command()).to match(/^ENVVAR=VAL /) + end + end + end +end