Class: VagrantPlugins::DSC::Provisioner

Inherits:
Object
  • Object
show all
Defined in:
lib/vagrant-dsc/provisioner.rb

Overview

DSC Provisioner Plugin.

Runs the [Desired State Configuration](technet.microsoft.com/en-au/library/dn249912.aspx) system on a guest Virtual Machine, enabling you to quickly configure & bootstrap a Windows Virtual Machine in a repeatable, reliable fashion - the Vagrant way.

Constant Summary collapse

PowerShell_VERSION =
4
DSC_GUEST_RUNNER_PATH =

Default path for storing the transient script runner This should be removed in cleanup

"c:/tmp/vagrant-dsc-runner.ps1"

Instance Method Summary collapse

Constructor Details

#initialize(machine, config) ⇒ Provisioner

Constructs the Provisioner Plugin.

Parameters:

  • machine (Machine)

    The guest machine that is to be provisioned.

  • config (Config)

    The Configuration object used by the Provisioner.



33
34
35
36
37
# File 'lib/vagrant-dsc/provisioner.rb', line 33

def initialize(machine, config)
  super

  @logger = Log4r::Logger.new("vagrant::provisioners::dsc")
end

Instance Method Details

#cleanupObject

Cleanup after a destroy action.

This is the method called when destroying a machine that allows for any state related to the machine created by the provisioner to be cleaned up.



155
156
157
# File 'lib/vagrant-dsc/provisioner.rb', line 155

def cleanup
  # Remove temp files? Or is this ONLY called in destroy (in which case those files will go anyway...)
end

#configure(root_config) ⇒ Object

Configures the Provisioner.

Parameters:

  • root_config (Config)

    The default configuration from the Vagrant hierarchy.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/vagrant-dsc/provisioner.rb', line 42

def configure(root_config)
  @logger.info("==> Configuring DSC")

  # Calculate the paths we're going to use based on the environment
  root_path = @machine.env.root_path
  @expanded_module_paths   = @config.expanded_module_paths(root_path)

  # Setup the module paths
  @module_paths = []
  @expanded_module_paths.each_with_index do |path, i|
    @module_paths << [path, File.join(config.temp_dir, "modules-#{i}")]
  end

  folder_opts = {}
  folder_opts[:type] = @config.synced_folder_type if @config.synced_folder_type
  folder_opts[:owner] = "root" if !@config.synced_folder_type

  # Share the manifests directory with the guest
  @logger.info("==> Sharing manifest #{File.expand_path(@config.manifests_path, root_path)} | #{manifests_guest_path} | #{folder_opts}")

  root_config.vm.synced_folder(
    File.expand_path(@config.manifests_path, root_path),
    manifests_guest_path, folder_opts)

  # Share the module paths
  @module_paths.each do |from, to|
    @logger.info("==> Sharing module folders #{from} | #{to}")
    root_config.vm.synced_folder(from, to, folder_opts)
  end
end

#generate_dsc_runner_scriptString

Generates a PowerShell DSC runner script from an ERB template

Returns:

  • (String)

    The interpolated PowerShell script.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/vagrant-dsc/provisioner.rb', line 201

def generate_dsc_runner_script
  path = File.expand_path("../templates/runner.ps1", __FILE__)

  script = Vagrant::Util::TemplateRenderer.render(path, options: {
      config: @config,
      module_paths: @module_paths.map { |k,v| v }.join(";"),
      mof_path: @config.mof_path,
      configuration_file: @config.configuration_file,
      configuration_file_path: "#{@config.manifests_path}/#{File.basename @config.configuration_file}",
      configuration_data_file_path: @config.configuration_data_file,
      configuration_name: @config.configuration_name,
      manifests_path: @config.manifests_path,
      temp_path: @config.temp_dir,
      parameters: @config.configuration_params.map { |k,v| "#{k}" + (!v.nil? ? " \"#{v}\"": '') }.join(" ")
  })
end

#get_configuration_statusObject



138
139
140
141
# File 'lib/vagrant-dsc/provisioner.rb', line 138

def get_configuration_status
  status = @machine.communicate.shell.powershell("(Get-DscConfigurationStatus).Status")
  return status[:data][0][:stdout]
end

#get_lcm_stateObject



133
134
135
136
# File 'lib/vagrant-dsc/provisioner.rb', line 133

def get_lcm_state
  state = @machine.communicate.shell.powershell("(Get-DscLocalConfigurationManager).LCMState")
  return state[:data][0][:stdout]
end

#install_dscObject

Install and Configure DSC where possible.

Operation is current unsupported, but is likely to be enabled as a flag when the plugin detects an unsupported OS.



188
189
190
191
192
193
194
195
196
# File 'lib/vagrant-dsc/provisioner.rb', line 188

def install_dsc
  # raise DSCError, I18n.t("vagrant_dsc.errors.manifest_missing", operation: "install_dsc")
  raise DSCUnsupportedOperation,  :operation => "install_dsc"
  # Install chocolatey

  # Ensure .NET 4.5 installed

  # Ensure WMF 4.0 is installed
end

#manifests_guest_pathObject

Local path (guest path) to the manifests directory.



160
161
162
# File 'lib/vagrant-dsc/provisioner.rb', line 160

def manifests_guest_path
    File.join(config.temp_dir, config.manifests_path)
end

#provisionObject

Provision the guest machine with DSC.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/vagrant-dsc/provisioner.rb', line 74

def provision
  @logger.info("==> Provisioning DSC man! #{Vagrant.source_root}")

  # If the machine has a wait for reboot functionality, then
  # do that (primarily Windows)
  if @machine.guest.capability?(:wait_for_reboot)
    @machine.guest.capability(:wait_for_reboot)
  end

  # Check that the shared folders are properly shared
  check = []
  check << manifests_guest_path
  @module_paths.each do |host_path, guest_path|
    check << guest_path
  end

  # Make sure the temporary directory is properly set up
  @machine.communicate.tap do |comm|
    comm.sudo("mkdir -p #{config.temp_dir}")
    comm.sudo("chmod 0777 #{config.temp_dir}")
  end

  verify_shared_folders(check)

  verify_dsc

  write_dsc_runner_script(generate_dsc_runner_script)

  run_dsc_apply

  wait_for_dsc_completion
end

#run_dsc_applyObject

Runs the DSC Configuration on the guest machine.



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/vagrant-dsc/provisioner.rb', line 239

def run_dsc_apply

  @machine.ui.info(I18n.t(
    "vagrant_dsc.running_dsc",
    manifest: config.configuration_file))

  # A bit of an ugly dance, but this is how we get neat, colourised output and exit codes from a Powershell run
  last_type = nil
  new_line = ""
  error = false
  machine.communicate.shell.powershell("powershell -ExecutionPolicy Bypass -OutputFormat Text -file #{DSC_GUEST_RUNNER_PATH}") do |type, data|
    if !data.chomp.empty?
      error = true if type == :stderr
      if [:stderr, :stdout].include?(type)
        color = type == :stdout ? :green : :red
        new_line = "\r\n" if last_type != nil and last_type != type
        last_type = type
        @machine.ui.info( new_line + data.chomp, color: color, new_line: false, prefix: false)
      end
    end
  end

  error == false
end

#show_dsc_failure_messageObject



143
144
145
146
147
148
# File 'lib/vagrant-dsc/provisioner.rb', line 143

def show_dsc_failure_message
  dsc_error_ps = "Get-WinEvent \"Microsoft-Windows-Dsc/Operational\" | Where-Object {$_.LevelDisplayName -eq \"Error\" -and $_.Message.StartsWith(\"Job $((Get-DscConfigurationStatus).JobId)\" )} | foreach { $_.Message }"
  @machine.communicate.shell.powershell(dsc_error_ps) do |type,data|
    @machine.ui.error(data, prefix: false)
  end
end

#verify_binary(binary) ⇒ Object

Verify the DSC binary is executable on the guest machine.



176
177
178
179
180
181
182
# File 'lib/vagrant-dsc/provisioner.rb', line 176

def verify_binary(binary)
  @machine.communicate.sudo(
    "which #{binary}",
    error_class: DSCError,
    error_key: :dsc_not_detected,
    binary: binary)
end

#verify_dscObject

Verify that a current version of WMF/Powershell is enabled on the guest.



165
166
167
168
169
170
171
172
173
# File 'lib/vagrant-dsc/provisioner.rb', line 165

def verify_dsc
  verify_binary("Start-DscConfiguration")

  # Confirm WMF 4.0+ in $PSVersionTable
  @machine.communicate.test(
      "(($PSVersionTable | ConvertTo-json | ConvertFrom-Json).PSVersion.Major) -ge #{PowerShell_VERSION}",
      error_class: DSCError,
      error_key: :dsc_incorrect_PowerShell_version )
end

#verify_shared_folders(folders) ⇒ Object

Verify that the shared folders have been properly configured on the guest machine.



266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/vagrant-dsc/provisioner.rb', line 266

def verify_shared_folders(folders)
  folders.each do |folder|
    # Warm up PoSH communicator for new instances - any stderr results
    # in failure: https://github.com/mefellows/vagrant-dsc/issues/21
    @machine.communicate.test("test -d #{folder}", sudo: true)

    @logger.info("Checking for shared folder: #{folder}")
    if !@machine.communicate.test("test -d #{folder}", sudo: true)
      raise DSCError, :missing_shared_folders
    end
  end
end

#wait_for_dsc_completionObject

Waits for the completion of the dsc configuration if dsc needs reboots. This currntly only works for WMF5 and needs wait_for_reboot



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/vagrant-dsc/provisioner.rb', line 108

def wait_for_dsc_completion
  return if Vagrant::Util::PowerShell.version.to_i < 5 || !@machine.guest.capability?(:wait_for_reboot)

  dsc_running = true

  while dsc_running
    case get_lcm_state
      when "PendingReboot"
        @machine.ui.info("DSC needs reboot. Wait for the completion of DSC.")
        @machine.guest.capability(:wait_for_reboot)

      # Do not Know a way to reattch to dsc job, therefore check periodically the state
      when "Busy"
        sleep 10
      else
        dsc_running = false
    end
  end

  if (get_configuration_status == "Failure")
    @machine.ui.error(I18n.t("failure_status"))
    show_dsc_failure_message
  end
end

#windows?Boolean

If on using WinRM, we can assume we are on Windows

Returns:

  • (Boolean)


280
281
282
# File 'lib/vagrant-dsc/provisioner.rb', line 280

def windows?
  @machine.config.vm.communicator == :winrm
end

#write_dsc_runner_script(script) ⇒ String

Writes the PowerShell DSC runner script to a location on the guest.

Parameters:

  • script (String)

    The PowerShell DSC runner script.

Returns:

  • (String)

    the Path to the uploaded location on the guest machine.



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/vagrant-dsc/provisioner.rb', line 222

def write_dsc_runner_script(script)
  guest_script_path = DSC_GUEST_RUNNER_PATH
  # TODO: Get a counter in here in case of multiple runs
  file = Tempfile.new(["vagrant-dsc-runner", "ps1"])
  begin
    file.write(script)
    file.fsync
    file.close
    @machine.communicate.upload(file.path, guest_script_path)
  ensure
    file.close
    file.unlink
  end
  guest_script_path
end