Merge pull request #152 from matt-richardson/support-winrm

Support Windows & WinRM [WIP]
This commit is contained in:
David Justice 2017-02-01 10:50:16 -08:00 коммит произвёл GitHub
Родитель 9684137661 1bc09b48cd
Коммит 96da70c61d
12 изменённых файлов: 257 добавлений и 32 удалений

Просмотреть файл

@ -75,6 +75,16 @@ module VagrantPlugins
end
end
# This action is called to read the WinRM info of the machine. The
# resulting state is expected to be put into the `:machine_winrm_info` key.
def self.action_read_winrm_info
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use ConnectAzure
b.use ReadWinrmInfo
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.
@ -178,6 +188,7 @@ module VagrantPlugins
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 :ReadWinrmInfo, action_root.join('read_winrm_info')
autoload :ReadState, action_root.join('read_state')
autoload :RestartVM, action_root.join('restart_vm')
autoload :RunInstance, action_root.join('run_instance')

Просмотреть файл

@ -0,0 +1,38 @@
#--------------------------------------------------------------------------
# Copyright (c) Microsoft Open Technologies, Inc.
# All Rights Reserved. Licensed under the Apache License, Version 2.0.
# See License.txt in the project root for license information.
#--------------------------------------------------------------------------
require 'log4r'
require 'vagrant-azure/util/machine_id_helper'
module VagrantPlugins
module Azure
module Action
class ReadWinrmInfo
include VagrantPlugins::Azure::Util::MachineIdHelper
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new('vagrant_azure::action::read_winrm_info')
end
def call(env)
if env[:machine].config.vm.guest == :windows
env[:machine_winrm_info] = read_winrm_info(env[:azure_arm_service], env)
end
@app.call(env)
end
def read_winrm_info(azure, env)
return nil if env[:machine].id.nil?
parsed = parse_machine_id(env[:machine].id)
public_ip = azure.network.public_ipaddresses.get(parsed[:group], "#{parsed[:name]}-vagrantPublicIP").value!.body
{:host => public_ip.properties.dns_settings.fqdn, :port => env[:machine].config.winrm.port}
end
end
end
end
end

Просмотреть файл

@ -28,19 +28,25 @@ module VagrantPlugins
machine = env[:machine]
# Get the configs
config = machine.provider_config
endpoint = config.endpoint
resource_group_name = config.resource_group_name
location = config.location
admin_user_name = machine.config.ssh.username
vm_name = config.vm_name
vm_password = config.vm_password
vm_size = config.vm_size
vm_image_urn = config.vm_image_urn
virtual_network_name = config.virtual_network_name
subnet_name = config.subnet_name
tcp_endpoints = config.tcp_endpoints
availability_set_name = config.availability_set_name
config = machine.provider_config
endpoint = config.endpoint
resource_group_name = config.resource_group_name
location = config.location
ssh_user_name = machine.config.ssh.username
vm_name = config.vm_name
vm_password = config.vm_password
vm_size = config.vm_size
vm_image_urn = config.vm_image_urn
virtual_network_name = config.virtual_network_name
subnet_name = config.subnet_name
tcp_endpoints = config.tcp_endpoints
availability_set_name = config.availability_set_name
admin_user_name = config.admin_username
admin_password = config.admin_password
winrm_port = machine.config.winrm.port
winrm_install_self_signed_cert = config.winrm_install_self_signed_cert
dns_label_prefix = Haikunator.haikunate(100)
deployment_template = config.deployment_template
# Launch!
env[:ui].info(I18n.t('vagrant_azure.launching_instance'))
@ -48,7 +54,8 @@ module VagrantPlugins
env[:ui].info(" -- Subscription Id: #{config.subscription_id}")
env[:ui].info(" -- Resource Group Name: #{resource_group_name}")
env[:ui].info(" -- Location: #{location}")
env[:ui].info(" -- Admin User Name: #{admin_user_name}") if admin_user_name
env[:ui].info(" -- SSH User Name: #{ssh_user_name}") if ssh_user_name
env[:ui].info(" -- Admin Username: #{admin_user_name}") if admin_user_name
env[:ui].info(" -- VM Name: #{vm_name}")
env[:ui].info(" -- VM Size: #{vm_size}")
env[:ui].info(" -- Image URN: #{vm_image_urn}")
@ -56,6 +63,7 @@ module VagrantPlugins
env[:ui].info(" -- Subnet Name: #{subnet_name}") if subnet_name
env[:ui].info(" -- TCP Endpoints: #{tcp_endpoints}") if tcp_endpoints
env[:ui].info(" -- Availability Set Name: #{availability_set_name}") if availability_set_name
env[:ui].info(" -- DNS Label Prefix: #{dns_label_prefix}")
image_publisher, image_offer, image_sku, image_version = vm_image_urn.split(':')
@ -67,8 +75,7 @@ module VagrantPlugins
@logger.info("Time to fetch os image details: #{env[:metrics]['get_image_details']}")
deployment_params = {
adminUserName: admin_user_name,
dnsLabelPrefix: Haikunator.haikunate(100),
dnsLabelPrefix: dns_label_prefix,
vmSize: vm_size,
vmName: vm_name,
imagePublisher: image_publisher,
@ -76,10 +83,22 @@ module VagrantPlugins
imageSku: image_sku,
imageVersion: image_version,
subnetName: subnet_name,
virtualNetworkName: virtual_network_name
virtualNetworkName: virtual_network_name,
}
if get_image_os(image_details) != 'Windows'
# we need to pass different parameters depending upon the OS
operating_system = get_image_os(image_details)
template_params = {
operating_system: operating_system,
winrm_install_self_signed_cert: winrm_install_self_signed_cert,
winrm_port: winrm_port,
dns_label_prefix: dns_label_prefix,
location: location,
deployment_template: deployment_template
}
if operating_system != 'Windows'
private_key_paths = machine.config.ssh.private_key_path
if private_key_paths.nil? || private_key_paths.empty?
raise I18n.t('vagrant_azure.private_key_not_specified')
@ -87,13 +106,23 @@ module VagrantPlugins
paths_to_pub = private_key_paths.map{ |k| File.expand_path( k + '.pub') }.select{ |p| File.exists?(p) }
raise I18n.t('vagrant_azure.public_key_path_private_key', private_key_paths.join(', ')) if paths_to_pub.empty?
deployment_params.merge!(adminUsername: ssh_user_name)
deployment_params.merge!(sshKeyData: File.read(paths_to_pub.first))
communicator_message = 'vagrant_azure.waiting_for_ssh'
else
env[:machine].config.vm.communicator = :winrm
machine.config.winrm.port = winrm_port
machine.config.winrm.username = admin_user_name
machine.config.winrm.password = admin_password
communicator_message = 'vagrant_azure.waiting_for_winrm'
windows_params = {
adminUsername: admin_user_name,
adminPassword: admin_password,
winRmPort: winrm_port
}
deployment_params.merge!(windows_params)
end
template_params = {
operating_system: get_image_os(image_details)
}
env[:ui].info(" -- Create or Update of Resource Group: #{resource_group_name}")
env[:metrics]['put_resource_group'] = Util::Timer.time do
put_resource_group(azure, resource_group_name, location)
@ -114,8 +143,8 @@ module VagrantPlugins
@logger.info("Time to deploy: #{env[:metrics]['deployment_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_azure.waiting_for_ssh'))
# Wait for SSH/WinRM to be ready.
env[:ui].info(I18n.t(communicator_message))
network_ready_retries = 0
network_ready_retries_max = 10
while true
@ -125,7 +154,7 @@ module VagrantPlugins
rescue Exception => e
if network_ready_retries < network_ready_retries_max
network_ready_retries += 1
@logger.warn(I18n.t('vagrant_azure.waiting_for_ssh, retrying'))
@logger.warn(I18n.t("#{communicator_message}, retrying"))
else
raise e
end
@ -134,10 +163,10 @@ module VagrantPlugins
end
end
@logger.info("Time for SSH ready: #{env[:metrics]['instance_ssh_time']}")
@logger.info("Time for SSH/WinRM ready: #{env[:metrics]['instance_ssh_time']}")
# Ready and booted!
env[:ui].info(I18n.t('vagrant_azure.ready'))
env[:ui].info(I18n.t('vagrant_azure.ready')) unless env[:interrupted]
end
# Terminate the instance if we were interrupted
@ -173,13 +202,25 @@ module VagrantPlugins
# This method generates the deployment template
def render_deployment_template(options)
Vagrant::Util::TemplateRenderer.render('arm/deployment.json', options.merge(template_root: template_root))
if options[:operating_system] == 'Windows' && options[:winrm_install_self_signed_cert]
setup_winrm_powershell = Vagrant::Util::TemplateRenderer.render('arm/setup-winrm.ps1', options.merge({template_root: template_root}))
encoded_setup_winrm_powershell = setup_winrm_powershell.
gsub("'", "', variables('singleQuote'), '").
gsub("\r\n", "\n").
gsub("\n", "; ")
self_signed_cert_resource = Vagrant::Util::TemplateRenderer.render('arm/selfsignedcert.json', options.merge({template_root: template_root, setup_winrm_powershell: encoded_setup_winrm_powershell}))
end
Vagrant::Util::TemplateRenderer.render('arm/deployment.json', options.merge({ template_root: template_root, self_signed_cert_resource: self_signed_cert_resource}))
end
def build_deployment_params(template_params, deployment_params)
params = ::Azure::ARM::Resources::Models::Deployment.new
params.properties = ::Azure::ARM::Resources::Models::DeploymentProperties.new
params.properties.template = JSON.parse(render_deployment_template(template_params))
if (template_params[:deployment_template].nil?)
params.properties.template = JSON.parse(render_deployment_template(template_params))
else
params.properties.template = JSON.parse(template_params[:deployment_template])
end
params.properties.mode = ::Azure::ARM::Resources::Models::DeploymentMode::Incremental
params.properties.parameters = build_parameters(deployment_params)
params

Просмотреть файл

@ -20,7 +20,9 @@ module VagrantPlugins
begin
env[:ui].info(I18n.t('vagrant_azure.terminating', parsed))
env[:ui].info('Deleting resource group')
env[:azure_arm_service].resources.resource_groups.delete(parsed[:group]).value!.body
env[:ui].info('Resource group deleted...')
rescue MsRestAzure::AzureOperationError => ex
unless ex.response.status == 404
raise ex

Просмотреть файл

@ -0,0 +1,12 @@
module VagrantPlugins
module Azure
module Cap
class WinRM
def self.winrm_info(machine)
env = machine.action('read_winrm_info')
env[:machine_winrm_info]
end
end
end
end
end

Просмотреть файл

@ -93,6 +93,26 @@ module VagrantPlugins
# @return [String]
attr_accessor :endpoint
# (Optional - requrired for Windows) The admin username for Windows templates -- ENV['AZURE_VM_ADMIN_USERNAME']
#
# @return [String]
attr_accessor :admin_username
# (Optional - Required for Windows) The admin username for Windows templates -- ENV['AZURE_VM_ADMIN_PASSWORD']
#
# @return [String]
attr_accessor :admin_password
# (Optional) Whether to automatically install a self-signed cert and open the firewall port for winrm over https -- default true
#
# @return [Bool]
attr_accessor :winrm_install_self_signed_cert
# (Optional - Required for Windows) The admin username for Windows templates -- ENV['AZURE_VM_ADMIN_PASSWORD']
#
# @return [String]
attr_accessor :deployment_template
def initialize
@tenant_id = UNSET_VALUE
@client_id = UNSET_VALUE
@ -111,6 +131,10 @@ module VagrantPlugins
@availability_set_name = UNSET_VALUE
@instance_ready_timeout = UNSET_VALUE
@instance_check_interval = UNSET_VALUE
@admin_username = UNSET_VALUE
@admin_password = UNSET_VALUE
@winrm_install_self_signed_cert = UNSET_VALUE
@deployment_template = UNSET_VALUE
end
def finalize!
@ -133,6 +157,11 @@ module VagrantPlugins
@instance_ready_timeout = 120 if @instance_ready_timeout == UNSET_VALUE
@instance_check_interval = 2 if @instance_check_interval == UNSET_VALUE
@admin_username = ENV['AZURE_VM_ADMIN_USERNAME'] if @admin_username == UNSET_VALUE
@admin_password = ENV['AZURE_VM_ADMIN_PASSWORD'] if @admin_password == UNSET_VALUE
@winrm_install_self_signed_cert = true if @winrm_install_self_signed_cert == UNSET_VALUE
@deployment_template = nil if @deployment_template == UNSET_VALUE
end
def validate(machine)

Просмотреть файл

@ -37,6 +37,11 @@ module VagrantPlugins
Provider
end
provider_capability(:azure, :winrm_info) do
require_relative 'capabilities/winrm'
VagrantPlugins::Azure::Cap::WinRM
end
def self.setup_i18n
I18n.load_path << File.expand_path(
'locales/en.yml',

Просмотреть файл

@ -10,6 +10,12 @@ module VagrantPlugins
def initialize(machine)
@machine = machine
# Load the driver
machine_id_changed
@machine.config.winrm.password = @machine.provider_config.admin_password
@machine.config.winrm.username = @machine.provider_config.admin_username
end
def action(name)
@ -29,6 +35,11 @@ module VagrantPlugins
env[:machine_ssh_info]
end
def winrm_info
env = @machine.action('read_winrm_info')
env[:machine_winrm_info]
end
def state
# Run a custom action we define called "read_state" which does what it
# says. It puts the state in the `:machine_state_id` key in the env

Просмотреть файл

@ -92,6 +92,8 @@ en:
you can run `vagrant destroy`.
waiting_for_ssh: |-
Waiting for SSH to become available...
waiting_for_winrm: |-
Waiting for WinRM to become available...
ready: |-
Machine is booted and ready for use!
public_key_path_private_key: |-

Просмотреть файл

@ -11,18 +11,19 @@
},
<% if operating_system == 'Windows' %>
"adminPassword": {
"type": "string",
"type": "securestring",
"metadata": {
"description": "Password for the Virtual Machine (only used on Windows)"
}
},
<% end %>
<% else %>
"sshKeyData": {
"type": "string",
"metadata": {
"description": "SSH rsa public key file as a string."
}
},
<% end %>
"dnsLabelPrefix": {
"type": "string",
"metadata": {
@ -83,6 +84,13 @@
"metadata": {
"description": "Name of the virtual network"
}
},
"winRmPort": {
"type": "int",
"defaultValue": 5986,
"metadata": {
"description": "WinRM port"
}
}
},
"variables": {
@ -100,7 +108,9 @@
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('subnetName'))]",
"apiVersion": "2015-06-15"
"apiVersion": "2015-06-15",
"singleQuote": "'",
"doubleQuote": "\""
},
"resources": [
{
@ -119,6 +129,36 @@
"location": "[variables('location')]",
"properties": {
"securityRules": [
<% if operating_system == 'Windows' %>
{
"name": "rdp_rule",
"properties": {
"description": "Enable Inbound RDP",
"protocol": "Tcp",
"sourcePortRange": "*",
"destinationPortRange": "3389",
"sourceAddressPrefix": "*",
"destinationAddressPrefix": "*",
"access": "Allow",
"priority": 123,
"direction": "Inbound"
}
},
{
"name": "winrm_rule",
"properties": {
"description": "Enable Inbound WinRM",
"protocol": "Tcp",
"sourcePortRange": "*",
"destinationPortRange": "[parameters('winRmPort')]",
"sourceAddressPrefix": "*",
"destinationAddressPrefix": "*",
"access": "Allow",
"priority": 124,
"direction": "Inbound"
}
}
<% else %>
{
"name": "ssh_rule",
"properties": {
@ -133,6 +173,7 @@
"direction": "Inbound"
}
}
<% end %>
]
}
},
@ -210,6 +251,9 @@
"[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
<% if operating_system == 'Windows' %>
<%= self_signed_cert_resource %>
<% end %>
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
@ -217,6 +261,9 @@
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
<% if operating_system == 'Windows' %>
"adminPassword": "[parameters('adminPassword')]"
<% else %>
"linuxConfiguration": {
"disablePasswordAuthentication": "true",
"ssh": {
@ -228,6 +275,7 @@
]
}
}
<% end %>
},
"storageProfile": {
"imageReference": {

Просмотреть файл

@ -0,0 +1,19 @@
"resources": [
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('vmName'),'/WinRMCustomScriptExtension')]",
"apiVersion": "[variables('apiVersion')]",
"location": "[resourceGroup().location]",
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'))]"
],
"properties": {
"publisher": "Microsoft.Compute",
"type": "CustomScriptExtension",
"typeHandlerVersion": "1.4",
"settings": {
"commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -command ', variables('doubleQuote'), '& { <%= setup_winrm_powershell %> }', variables('doubleQuote'))]"
}
}
}
],

Просмотреть файл

@ -0,0 +1,7 @@
$hostname = '<%= dns_label_prefix %>.<%= location %>.cloudapp.azure.com'
$Cert = (New-SelfSignedCertificate -CertstoreLocation Cert:/LocalMachine/My -DnsName $hostname).Thumbprint
$transport = New-Item -Path WSMan:/LocalHost/Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert -Force
cd $transport.PSPath
set-item ./HostName -value $hostname -force
set-item ./Port -value <%= winrm_port %> -force
netsh advfirewall firewall add rule name=WinRM_HTTPS dir=in action=allow protocol=TCP localport=<%= winrm_port %>