Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Windows hosts through WinRM #216

Merged
merged 1 commit into from
May 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lib/vagrant-google/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ def self.action_read_ssh_info
end
end

# This action is called to setup the Windows user/password on the machine.
def self.action_setup_winrm_password
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use ConnectGoogle
b.use SetupWinrmPassword
end
end

# This action is called to read the state of the machine. The
# resulting state is expected to be put into the `:machine_state_id`
# key.
Expand Down Expand Up @@ -182,6 +191,7 @@ def self.action_reload
autoload :MessageNotCreated, action_root.join("message_not_created")
autoload :MessageWillNotDestroy, action_root.join("message_will_not_destroy")
autoload :ReadSSHInfo, action_root.join("read_ssh_info")
autoload :SetupWinrmPassword, action_root.join('setup_winrm_password')
autoload :ReadState, action_root.join("read_state")
autoload :RunInstance, action_root.join("run_instance")
autoload :StartInstance, action_root.join("start_instance")
Expand Down
27 changes: 22 additions & 5 deletions lib/vagrant-google/action/run_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
require "log4r"
require 'vagrant/util/retryable'
require 'vagrant-google/util/timer'
require 'vagrant-google/action/setup_winrm_password'

module VagrantPlugins
module Google
Expand Down Expand Up @@ -287,19 +288,35 @@ def call(env) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
env[:interrupted] = true
end

# Parse out the image project in case it was not set
# and check if it is part of a public windows project
img_project = image.split("/")[6]
is_windows_image = img_project.eql?("windows-cloud") || img_project.eql?("windows-sql-cloud")

# Reset the password if a windows image unless flag overrides
setup_winrm_password = zone_config.setup_winrm_password
if setup_winrm_password.nil? && is_windows_image
setup_winrm_password = true
end

if setup_winrm_password
env[:ui].info("Setting up WinRM Password")
env[:action_runner].run(Action.action_setup_winrm_password, env)
end

unless env[:terminated]
Copy link
Collaborator

@Temikus Temikus Apr 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So while debugging I noticed that winrm options are not really set:

ls @machine.winrm_info
NoMethodError: undefined method `winrm_info' for #<Vagrant::Machine: winserver (VagrantPlugins::Google::Provider)>
from /Users/temikus/.rbenv/versions/2.4.3/lib/ruby/gems/2.4.0/gems/pry-0.12.2/lib/pry/core_extensions.rb:17:in `__pry__'

I suspect it may be because we never set up the winrm capability in the provider, like Azure did:

https://github.com/Azure/vagrant-azure/blob/97f4d125aebcb5669e9d1278b702e13554bef640/lib/vagrant-azure/capabilities/winrm.rb

I'm raising this since this may cause issues in the future as Vagrant has some autodetect logic which this may break.

Can you take a look?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had originally had all the features from the Azure plugin included, like this capability file but refactored it out when it didn't seem to do anything. I am confused as to what the difference is between including the details in the config (e.g. config.winrm.*) and the need for setting up env[:machine_winrm_info] via this capability.

The WinRM communicator does not appear to need this winrm_info, using the details from the Vagrantfile config instead. However, I do see that there is a machine_ssh_info, so it would make sense to mimic that with WinRM.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some more digging, I've found that if I add in a read_winrm_info functionality, I am actually breaking things. I've found that in the following file, it has a default way of computing winrm_info if it is not explicitly defined. That must be why it works without adding in this functionality.

https://github.com/hashicorp/vagrant/blob/2c4a40fccbcd1869c7ea457d6f251c67c30eda71/plugins/communicators/winrm/helper.rb#L9

Would this be sufficient? Else I can explicitly add in this functionality, though I am still troubleshooting it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should've been more clear here, sorry m(_ _)m

So, ideally we would want the provider to explicitly set that guest capability to make sure we don't rely on heuristics. However, if it's too difficult I'm also open leaving that as an explicit TODO in the code and creating an issue for the future.

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take another shot at implementing this and see if I can get it working correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go ahead and use the default heuristics.

env[:metrics]["instance_ssh_time"] = Util::Timer.time do
# Wait for SSH to be ready.
env[:ui].info(I18n.t("vagrant_google.waiting_for_ssh"))
env[:metrics]["instance_comm_time"] = Util::Timer.time do
# Wait for Comms to be ready.
env[:ui].info(I18n.t("vagrant_google.waiting_for_comm"))
while true
# If we're interrupted just back out
break if env[:interrupted]
break if env[:machine].communicate.ready?
sleep 2
end
end
@logger.info("Time for SSH ready: #{env[:metrics]["instance_ssh_time"]}")
env[:ui].info(I18n.t("vagrant_google.ready_ssh")) unless env[:interrupted]
@logger.info("Time for Comms ready: #{env[:metrics]["instance_comm_time"]}")
env[:ui].info(I18n.t("vagrant_google.ready_comm")) unless env[:interrupted]
end

# Terminate the instance if we were interrupted
Expand Down
72 changes: 72 additions & 0 deletions lib/vagrant-google/action/setup_winrm_password.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Changes:
# April 2019: Modified example found here:
# https://github.com/GoogleCloudPlatform/compute-image-windows/blob/master/examples/windows_auth_python_sample.py
# to enable WinRM with vagrant.

module VagrantPlugins
module Google
module Action
# Sets up a temporary WinRM password using Google's method for
# establishing a new password over encrypted channels.
class SetupWinrmPassword
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant_google::action::setup_winrm_password")
end

def setup_password(env, instance, zone, user)
# Setup
compute = env[:google_compute]
server = compute.servers.get(instance, zone)
password = server.reset_windows_password(user)

env[:ui].info("Temp Password: #{password}")

password
end

def call(env)
# Get the configs
zone = env[:machine].provider_config.zone
zone_config = env[:machine].provider_config.get_zone_config(zone)

instance = zone_config.name
user = env[:machine].config.winrm.username
pass = env[:machine].config.winrm.password

# Get Temporary Password, set WinRM password
temp_pass = setup_password(env, instance, zone, user)
env[:machine].config.winrm.password = temp_pass

# Wait for WinRM To be Ready
env[:ui].info("Waiting for WinRM To be ready")
env[:machine].communicate.wait_for_ready(60)

# Use WinRM to Change Password to one in Vagrantfile
env[:ui].info("Changing password from temporary to winrm password")
winrmcomm = VagrantPlugins::CommunicatorWinRM::Communicator.new(env[:machine])
cmd = "net user #{user} #{pass}"
opts = { elevated: true }
winrmcomm.test(cmd, opts)

# Update WinRM password to reflect updated one
env[:machine].config.winrm.password = pass
end
end
end
end
end
8 changes: 4 additions & 4 deletions lib/vagrant-google/action/start_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ def call(env)
@logger.info("Time to instance ready: #{env[:metrics]["instance_ready_time"]}")

unless env[:interrupted]
env[:metrics]["instance_ssh_time"] = Util::Timer.time do
# Wait for SSH to be ready.
env[:ui].info(I18n.t("vagrant_google.waiting_for_ssh"))
env[:metrics]["instance_comm_time"] = Util::Timer.time do
# Wait for Comms to be ready.
env[:ui].info(I18n.t("vagrant_google.waiting_for_comm"))
while true
# If we're interrupted then just back out
break if env[:interrupted]
Expand All @@ -76,7 +76,7 @@ def call(env)
end
end

@logger.info("Time for SSH ready: #{env[:metrics]["instance_ssh_time"]}")
@logger.info("Time for Comms ready: #{env[:metrics]["instance_comm_time"]}")

# Ready and booted!
env[:ui].info(I18n.t("vagrant_google.ready"))
Expand Down
9 changes: 9 additions & 0 deletions lib/vagrant-google/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ class Config < Vagrant.plugin("2", :config) # rubocop:disable Metrics/ClassLengt
# @return [Array<Hash>]
attr_accessor :additional_disks

# (Optional - Override default WinRM setup before for Public Windows images)
#
# @return [Boolean]
attr_accessor :setup_winrm_password

def initialize(zone_specific=false)
@google_json_key_location = UNSET_VALUE
@google_project_id = UNSET_VALUE
Expand Down Expand Up @@ -210,6 +215,7 @@ def initialize(zone_specific=false)
@service_accounts = UNSET_VALUE
@service_account = UNSET_VALUE
@additional_disks = []
@setup_winrm_password = UNSET_VALUE

# Internal state (prefix with __ so they aren't automatically
# merged)
Expand Down Expand Up @@ -382,6 +388,9 @@ def finalize! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedC
# Default IAM service account
@service_account = nil if @service_account == UNSET_VALUE

# Default Setup WinRM Password
@setup_winrm_password = nil if @setup_winrm_password == UNSET_VALUE

# Config option service_accounts is deprecated
if @service_accounts
@scopes = @service_accounts
Expand Down
8 changes: 4 additions & 4 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ en:
Instance is not created. Please run `vagrant up` first.
ready: |-
Machine is booted and ready for use!
ready_ssh: |-
Machine is ready for SSH access!
ready_comm: |-
Machine is ready for Communicator access!
rsync_not_found_warning: |-
Warning! Folder sync disabled because the rsync binary is missing.
Make sure rsync is installed and the binary can be found in the PATH.
Expand All @@ -31,8 +31,8 @@ en:
Waiting for GCP operation '%{name}' to finish...
waiting_for_ready: |-
Waiting for instance to become "ready"...
waiting_for_ssh: |-
Waiting for SSH to become available...
waiting_for_comm: |-
Waiting for Communicator to become available...
warn_networks: |-
Warning! The Google provider doesn't support any of the Vagrant
high-level network configurations (`config.vm.network`). They
Expand Down
2 changes: 1 addition & 1 deletion vagrant-google.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Gem::Specification.new do |s|
s.required_rubygems_version = ">= 1.3.6"
s.rubyforge_project = "vagrant-google"

s.add_runtime_dependency "fog-google", "~> 1.9.1"
s.add_runtime_dependency "fog-google", "~> 1.10.0"

# This is a restriction to avoid errors on `failure_message_for_should`
# TODO: revise after vagrant_spec goes past >0.0.1 (at master@e623a56)
Expand Down