diff --git a/Gemfile b/Gemfile index b4c3891..db9ded4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,5 @@ # encoding: utf-8 -source 'https://rubygems.org' +source "https://rubygems.org" # Specify your gem's dependencies in kitchen-dsc.gemspec gemspec - -group :test do - gem 'rake' -end diff --git a/LICENSE b/LICENSE index 6226a05..4c444be 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -The MIT License (MIT) - -Copyright (c) 2014 Steven Murawski - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Steven Murawski + + 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. \ No newline at end of file diff --git a/Rakefile b/Rakefile index 41e01b4..a78066a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,52 +1,44 @@ # -*- encoding: utf-8 -*- -require 'bundler/gem_tasks' +require "bundler/gem_tasks" -require 'rake/testtask' +require "rake/testtask" Rake::TestTask.new(:unit) do |t| - t.libs.push 'lib' - t.test_files = FileList['spec/**/*_spec.rb'] + t.libs.push "lib" + t.test_files = FileList["spec/**/*_spec.rb"] t.verbose = true end -desc 'Run all test suites' -task test: [:unit] +desc "Run all test suites" +task :test => [:unit] -desc 'Display LOC stats' +desc "Display LOC stats" task :stats do puts "\n## Production Code Stats" - sh 'countloc -r lib' + sh "countloc -r lib" puts "\n## Test Code Stats" - sh 'countloc -r spec' + sh "countloc -r spec" end -require 'finstyle' -require 'rubocop/rake_task' +require "finstyle" +require "rubocop/rake_task" RuboCop::RakeTask.new(:style) do |task| - task.options << '--display-cop-names' - task.options << '--lint' - task.options << '--config' << '.rubocop.yml' - task.patterns = ['lib/**/*.rb'] + task.options << "--display-cop-names" + task.options << "--lint" + task.options << "--config" << ".rubocop.yml" + task.patterns = ["lib/**/*.rb"] end -require 'cane/rake_task' -desc 'Run cane to check quality metrics' +require "cane/rake_task" +desc "Run cane to check quality metrics" Cane::RakeTask.new do |cane| - cane.canefile = './.cane' + cane.canefile = "./.cane" end -desc 'Run all quality tasks' -task quality: [:cane, :style, :stats] +desc "Run all quality tasks" +task :quality => [:cane, :style, :stats] -require 'yard' +require "yard" YARD::Rake::YardocTask.new -desc 'Generate gem dependency graph' -task :viz do - Bundler.with_clean_env do - sh 'bundle viz --without test development guard ' \ - '--requirements --version' - end -end - -task default: [:test, :quality] +task :default => [:test, :quality] diff --git a/kitchen-dsc.gemspec b/kitchen-dsc.gemspec index df25f73..a9f0a6f 100755 --- a/kitchen-dsc.gemspec +++ b/kitchen-dsc.gemspec @@ -1,21 +1,21 @@ # encoding: utf-8 -$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) -require 'kitchen-dsc/version' +$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) +require "kitchen-dsc/version" Gem::Specification.new do |s| - s.name = 'kitchen-dsc' + s.name = "kitchen-dsc" s.version = Kitchen::Dsc::VERSION - s.authors = ['Steven Murawski'] - s.email = ['smurawski@chef.io'] - s.homepage = 'https://github.com/test-kitchen/kitchen-dsc' - s.summary = 'PowerShell DSC provisioner for test-kitchen' - candidates = Dir.glob('lib/**/*') + ['README.md', 'kitchen-dsc.gemspec'] + s.authors = ["Steven Murawski"] + s.email = ["smurawski@chef.io"] + s.homepage = "https://github.com/test-kitchen/kitchen-dsc" + s.summary = "PowerShell DSC provisioner for test-kitchen" + candidates = Dir.glob("lib/**/*") + ["README.md", "kitchen-dsc.gemspec"] s.files = candidates.sort s.platform = Gem::Platform::RUBY - s.require_paths = ['lib'] - s.rubyforge_project = '[none]' - s.license = 'Apache 2' + s.require_paths = ["lib"] + s.rubyforge_project = "[none]" + s.license = "Apache 2" s.description = <<-EOF == DESCRIPTION: @@ -26,16 +26,23 @@ DSC Provisioner for Test Kitchen TBD EOF - s.add_dependency 'test-kitchen', '~> 1.10' - - s.add_development_dependency 'countloc', '~> 0.4' - s.add_development_dependency 'rake' - s.add_development_dependency 'rspec', '~> 3.2' - s.add_development_dependency 'simplecov', '~> 0.9' + s.add_dependency "test-kitchen", ">= 1.9" + s.add_dependency "dsc_lcm_configuration" + + s.add_development_dependency "countloc", "~> 0.4" + s.add_development_dependency "rake" + s.add_development_dependency "rspec", "~> 3.2" + s.add_development_dependency "simplecov", "~> 0.9" + s.add_development_dependency "minitest", "~> 5.3" + s.add_development_dependency "yard", "~> 0.8" + s.add_development_dependency "pry" + s.add_development_dependency "pry-stack_explorer" + s.add_development_dependency "pry-byebug" + s.add_development_dependency "rb-readline" # style and complexity libraries are tightly version pinned as newer releases # may introduce new and undesireable style choices which would be immediately # enforced in CI - s.add_development_dependency 'finstyle', '1.4.0' - s.add_development_dependency 'cane', '2.6.2' + s.add_development_dependency "finstyle", "1.4.0" + s.add_development_dependency "cane", "2.6.2" end diff --git a/lib/kitchen/provisioner/dsc.rb b/lib/kitchen/provisioner/dsc.rb index 54676e2..c361ceb 100755 --- a/lib/kitchen/provisioner/dsc.rb +++ b/lib/kitchen/provisioner/dsc.rb @@ -4,13 +4,14 @@ # # Copyright (C) 2014 Steven Murawski # -# Licensed under the MIT License. +# Licensed under the Apache 2 License. # See LICENSE for more details -require 'fileutils' -require 'pathname' -require 'kitchen/provisioner/base' -require 'kitchen/util' +require "fileutils" +require "pathname" +require "kitchen/provisioner/base" +require "kitchen/util" +require "dsc_lcm_configuration" module Kitchen module Provisioner @@ -19,15 +20,15 @@ class Dsc < Base attr_accessor :tmp_dir - default_config :modules_path, 'modules' + default_config :modules_path, "modules" - default_config :configuration_script_folder, 'examples' - default_config :configuration_script, 'dsc_configuration.ps1' + default_config :configuration_script_folder, "examples" + default_config :configuration_script, "dsc_configuration.ps1" default_config :configuration_name do |provisioner| provisioner.instance.suite.name end - default_config :configuration_data_variable, 'ConfigurationData' + default_config :configuration_data_variable, "ConfigurationData" default_config :configuration_data default_config :nuget_force_bootstrap, true @@ -35,97 +36,17 @@ class Dsc < Base default_config :gallery_name default_config :modules_from_gallery - default_config :dsc_local_configuration_manager_version, 'wmf4' - default_config :dsc_local_configuration_manager + default_config :dsc_local_configuration_manager_version, "wmf4" + default_config :dsc_local_configuration_manager, {} - def override_lcm_setting?(name) - if config[:dsc_local_configuration_manager].nil? || - config[:dsc_local_configuration_manager][name.to_sym].nil? - false - else - true - end - end - - def resolve_lcm_setting(name, default_value) - if override_lcm_setting? name - config[:dsc_local_configuration_manager][name.to_sym] - else - default_value - end - end - - def lcm_settings - { - action_after_reboot: (resolve_lcm_setting 'action_after_reboot', 'StopConfiguration'), - allow_module_overwrite: (resolve_lcm_setting 'allow_module_overwrite', false), - certificate_id: (resolve_lcm_setting 'certificate_id', nil), - configuration_mode: (resolve_lcm_setting 'configuration_mode', 'ApplyAndAutoCorrect'), - debug_mode: (resolve_lcm_setting 'debug_mode', 'All'), - reboot_if_needed: (resolve_lcm_setting 'reboot_if_needed', false), - refresh_mode: (resolve_lcm_setting 'refresh_mode', 'PUSH') - } + def finalize_config!(instance) + config[:dsc_local_configuration_manager] = lcm.lcm_config + super(instance) end def install_command - lcm_config = lcm_settings - case config[:dsc_local_configuration_manager_version] - when 'wmf4_legacy', 'wmf4' - lcm_configuration_script = <<-LCMSETUP - configuration SetupLCM - { - LocalConfigurationManager - { - AllowModuleOverwrite = [bool]::Parse('#{lcm_config[:allow_module_overwrite]}') - CertificateID = '#{lcm_config[:certificate_id].nil? ? '$null' : lcm_config[:certificate_id]}' - ConfigurationMode = '#{lcm_config[:configuration_mode]}' - ConfigurationModeFrequencyMins = #{lcm_config[:configuration_mode_frequency_mins].nil? ? '30' : lcm_config[:configuration_mode_frequency_mins]} - RebootNodeIfNeeded = [bool]::Parse('#{lcm_config[:reboot_if_needed]}') - RefreshFrequencyMins = #{lcm_config[:refresh_frequency_mins].nil? ? '15' : lcm_config[:refresh_frequency_mins]} - RefreshMode = '#{lcm_config[:refresh_mode]}' - } - } - LCMSETUP - when 'wmf4_with_update' - lcm_configuration_script = <<-LCMSETUP - configuration SetupLCM - { - LocalConfigurationManager - { - ActionAfterReboot = '#{lcm_config[:action_after_reboot]}' - AllowModuleOverwrite = [bool]::Parse('#{lcm_config[:allow_module_overwrite]}') - CertificateID = '#{lcm_config[:certificate_id].nil? ? '$null' : lcm_config[:certificate_id]}' - ConfigurationMode = '#{lcm_config[:configuration_mode]}' - ConfigurationModeFrequencyMins = #{lcm_config[:configuration_mode_frequency_mins].nil? ? '30' : lcm_config[:configuration_mode_frequency_mins]} - DebugMode = '#{lcm_config[:debug_mode]}' - RebootNodeIfNeeded = [bool]::Parse('#{lcm_config[:reboot_if_needed]}') - RefreshFrequencyMins = #{lcm_config[:refresh_frequency_mins].nil? ? '15' : lcm_config[:refresh_frequency_mins]} - RefreshMode = '#{lcm_config[:refresh_mode]}' - } - } - LCMSETUP - when 'wmf5' - lcm_configuration_script = <<-LCMSETUP - [DSCLocalConfigurationManager()] - configuration SetupLCM - { - Settings - { - ActionAfterReboot = '#{lcm_config[:action_after_reboot]}' - AllowModuleOverwrite = [bool]::Parse('#{lcm_config[:allow_module_overwrite]}') - CertificateID = '#{lcm_config[:certificate_id].nil? ? '$null' : lcm_config[:certificate_id]}' - ConfigurationMode = '#{lcm_config[:configuration_mode]}' - ConfigurationModeFrequencyMins = #{lcm_config[:configuration_mode_frequency_mins].nil? ? '15' : lcm_config[:configuration_mode_frequency_mins]} - DebugMode = '#{lcm_config[:debug_mode]}' - RebootNodeIfNeeded = [bool]::Parse('#{lcm_config[:reboot_if_needed]}') - RefreshFrequencyMins = #{lcm_config[:refresh_frequency_mins].nil? ? '30' : lcm_config[:refresh_frequency_mins]} - RefreshMode = '#{lcm_config[:refresh_mode]}' - } - } - LCMSETUP - end full_lcm_configuration_script = <<-EOH - #{lcm_configuration_script} + #{lcm.lcm_configuration_script} $null = SetupLCM Set-DscLocalConfigurationManager -Path ./SetupLCM | out-null @@ -133,63 +54,6 @@ def install_command wrap_powershell_code(full_lcm_configuration_script) end - # rubocop:enable Metrics/LineLength - - def setup_config_directory_script - "mkdir (split-path (join-path #{config[:root_path]} #{sandboxed_configuration_script})) -force | out-null" - end - - def powershell_module_params(module_specification_hash) - keys = module_specification_hash.keys.reject { |k| k.to_s.casecmp('force') == 0 } - unless keys.any? { |k| k.to_s.downcase == 'repository' } - keys.push(:repository) - module_specification_hash[:repository] = psmodule_repository_name - end - keys.map { |key| "-#{key} #{module_specification_hash[key]}" }.join(' ') - end - - def powershell_modules - Array(config[:modules_from_gallery]).map do |powershell_module| - params = if powershell_module.is_a? Hash - powershell_module_params(powershell_module) - else - "-name '#{powershell_module}' -Repository #{psmodule_repository_name}" - end - "install-module #{params} -force | out-null" - end - end - - def nuget_force_bootstrap - return unless config[:nuget_force_bootstrap] - info('Bootstrapping the nuget package provider for PowerShell PackageManagement.') - 'install-packageprovider nuget -force -forcebootstrap | out-null' - end - - def psmodule_repository_name - return 'PSGallery' if config[:gallery_name].nil? && config[:gallery_uri].nil? - return 'testing' if config[:gallery_name].nil? - config[:gallery_name] - end - - def register_psmodule_repository - return if config[:gallery_uri].nil? - info("Registering a new PowerShellGet Repository - #{psmodule_repository_name}") - "register-packagesource -providername PowerShellGet -name '#{psmodule_repository_name}' -location '#{config[:gallery_uri]}' -force -trusted" - end - - def install_module_script - return if config[:modules_from_gallery].nil? - <<-EOH - #{nuget_force_bootstrap} - #{register_psmodule_repository} - #{powershell_modules.join("\n")} - EOH - end - - def install_modules? - config[:dsc_local_configuration_manager_version] == 'wmf5' && - !config[:modules_from_gallery].nil? - end def init_command script = <<-EOH @@ -201,18 +65,18 @@ def init_command def create_sandbox super - info('Staging DSC Resource Modules for copy to the SUT') + info("Staging DSC Resource Modules for copy to the SUT") if powershell_module? prepare_resource_style_directory else prepare_repo_style_directory end - info('Staging DSC configuration script for copy to the SUT') + info("Staging DSC configuration script for copy to the SUT") prepare_configuration_script end def prepare_command - info('Moving DSC Resources onto PSModulePath') + info("Moving DSC Resources onto PSModulePath") info("Generating the MOF script for the configuration #{config[:configuration_name]}") stage_resources_and_generate_mof_script = <<-EOH if (Test-Path (join-path #{config[:root_path]} 'modules')) @@ -237,20 +101,11 @@ def prepare_command #{configuration_data_assignment unless config[:configuration_data].nil?} - $null = #{config[:configuration_name]} -outputpath c:/configurations #{'-configurationdata $' + configuration_data_variable} + $null = #{config[:configuration_name]} -outputpath c:/configurations #{"-configurationdata $" + configuration_data_variable} EOH debug("Shelling out: #{stage_resources_and_generate_mof_script}") wrap_powershell_code(stage_resources_and_generate_mof_script) end - # rubocop:enable Metrics/LineLength - - def configuration_data_variable - config[:configuration_data_variable].nil? ? 'ConfigurationData' : config[:configuration_data_variable] - end - - def configuration_data_assignment - '$' + configuration_data_variable + ' = ' + ps_hash(config[:configuration_data]) - end def run_command config[:retry_on_exit_code] = [35] if config[:retry_on_exit_code].empty? @@ -280,8 +135,80 @@ def run_command private + def lcm + @lcm ||= begin + lcm_version = config[:dsc_local_configuration_manager_version] + lcm_config = config[:dsc_local_configuration_manager] + DscLcmConfiguration::Factory.create(lcm_version, lcm_config) + end + end + + def setup_config_directory_script + "mkdir (split-path (join-path #{config[:root_path]} #{sandboxed_configuration_script})) -force | out-null" + end + + def powershell_module_params(module_specification_hash) + keys = module_specification_hash.keys.reject { |k| k.to_s.casecmp('force') == 0 } + unless keys.any? { |k| k.to_s.downcase == 'repository' } + keys.push(:repository) + module_specification_hash[:repository] = psmodule_repository_name + end + keys.map { |key| "-#{key} #{module_specification_hash[key]}" }.join(' ') + end + + def powershell_modules + Array(config[:modules_from_gallery]).map do |powershell_module| + params = if powershell_module.is_a? Hash + powershell_module_params(powershell_module) + else + "-name '#{powershell_module}' -Repository #{psmodule_repository_name}" + end + "install-module #{params} -force | out-null" + end + end + + def nuget_force_bootstrap + return unless config[:nuget_force_bootstrap] + info("Bootstrapping the nuget package provider for PowerShell PackageManagement.") + "install-packageprovider nuget -force -forcebootstrap | out-null" + end + + def psmodule_repository_name + return "PSGallery" if config[:gallery_name].nil? && config[:gallery_uri].nil? + return "testing" if config[:gallery_name].nil? + config[:gallery_name] + end + + def register_psmodule_repository + return if config[:gallery_uri].nil? + info("Registering a new PowerShellGet Repository - #{psmodule_repository_name}") + "register-packagesource -providername PowerShellGet -name '#{psmodule_repository_name}' -location '#{config[:gallery_uri]}' -force -trusted" + end + + def install_module_script + return if config[:modules_from_gallery].nil? + <<-EOH + #{nuget_force_bootstrap} + #{register_psmodule_repository} + #{powershell_modules.join("\n")} + EOH + end + + def install_modules? + config[:dsc_local_configuration_manager_version] == "wmf5" && + !config[:modules_from_gallery].nil? + end + + def configuration_data_variable + config[:configuration_data_variable].nil? ? "ConfigurationData" : config[:configuration_data_variable] + end + + def configuration_data_assignment + "$" + configuration_data_variable + " = " + ps_hash(config[:configuration_data]) + end + def wrap_powershell_code(code) - wrap_shell_code( [ "$ProgressPreference = 'SilentlyContinue';", code ].join("\n") ) + wrap_shell_code(["$ProgressPreference = 'SilentlyContinue';", code].join("\n")) end def powershell_module? @@ -290,11 +217,11 @@ def powershell_module? end def list_files(path) - base_directory_content = Dir.glob(File.join(path, '*')) - nested_directory_content = Dir.glob(File.join(path, '*/**/*')) + base_directory_content = Dir.glob(File.join(path, "*")) + nested_directory_content = Dir.glob(File.join(path, "*/**/*")) all_directory_content = [base_directory_content, nested_directory_content].flatten - ignore_files = ['Gemfile', 'Gemfile.lock', 'README.md', 'LICENSE.txt'] + ignore_files = ["Gemfile", "Gemfile.lock", "README.md", "LICENSE.txt"] all_directory_content.reject do |f| debug("Enumerating #{f}") ignore_files.include?(File.basename(f)) || File.directory?(f) @@ -310,17 +237,17 @@ def prepare_resource_style_directory base = config[:kitchen_root] list_files(base).each do |src| - dest = File.join(sandbox_base_module_path, src.sub("#{base}/", '')) + dest = File.join(sandbox_base_module_path, src.sub("#{base}/", "")) FileUtils.mkdir_p(File.dirname(dest)) debug("Staging #{src} ") debug(" at #{dest}") - FileUtils.cp(src, dest, preserve: true) + FileUtils.cp(src, dest, :preserve => true) end end def prepare_repo_style_directory module_path = File.join(config[:kitchen_root], config[:modules_path]) - sandbox_module_path = File.join(sandbox_path, 'modules') + sandbox_module_path = File.join(sandbox_path, "modules") if Dir.exist?(module_path) debug("Moving #{module_path} to #{sandbox_module_path}") @@ -331,23 +258,23 @@ def prepare_repo_style_directory end def sandboxed_configuration_script - File.join('configuration', config[:configuration_script]) + File.join("configuration", config[:configuration_script]) end def pad(depth = 0) - ' ' * depth + " " * depth end def ps_hash(obj, depth = 0) if obj.is_a?(Hash) obj.map do |k, v| - %(#{pad(depth + 2)}#{ps_hash(k)} = #{ps_hash(v, depth + 2)}) + %{#{pad(depth + 2)}#{ps_hash(k)} = #{ps_hash(v, depth + 2)}} end.join(";\n").insert(0, "@{\n").insert(-1, "\n#{pad(depth)}}") elsif obj.is_a?(Array) - array_string = obj.map { |v| ps_hash(v, depth + 4) }.join(',') + array_string = obj.map { |v| ps_hash(v, depth + 4) }.join(",") "#{pad(depth)}@(\n#{array_string}\n)" else - %("#{obj}") + %{"#{obj}"} end end diff --git a/lib/kitchen/provisioner/dsc_lcm/lcm_base.rb b/lib/kitchen/provisioner/dsc_lcm/lcm_base.rb new file mode 100644 index 0000000..872eb5d --- /dev/null +++ b/lib/kitchen/provisioner/dsc_lcm/lcm_base.rb @@ -0,0 +1,88 @@ +# -*- encoding: utf-8 -*- +# +# Author:: Steven Murawski () +# +# Copyright (C) 2014 Steven Murawski +# +# Licensed under the Apache 2 License. +# See LICENSE for more details + +require "kitchen/provisioner/dsc_lcm/lcm_v4" +require "kitchen/provisioner/dsc_lcm/lcm_v5" + +module Kitchen + module Provisioner + module DscLcm + class LcmBase + + + + def lcm_properties + { + :allow_module_overwrite => false, + :certificate_id => nil, + :configuration_mode => "ApplyAndAutoCorrect", + :configuration_mode_frequency_mins => 30, + :reboot_if_needed => false, + :refresh_mode => "PUSH", + :refresh_frequency_mins => 15 + } + end + + def initialize(config = {}) + @certificate_id = nil + lcm_properties.each do |setting, value| + send(setting, value) + end + + config.each do |setting, value| + send(setting, value) + end + end + + def method_missing(name, *args) + return super unless lcm_properties.keys.include?(name) + if args.length == 1 + instance_variable_set("@#{name}", args.first) + else + instance_variable_get("@#{name}") + end + end + + def certificate_id(value = nil) + if value.nil? + @certificate_id.nil? ? "$null" : "'#{@certificate_id}'" + else + @certificate_id = value + end + end + + def lcm_config + hash = {} + lcm_properties.keys.each do |key| + hash[key] = send(key) + end + hash + end + + def lcm_configuration_script + <<-LCMSETUP + configuration SetupLCM + { + LocalConfigurationManager + { + AllowModuleOverwrite = [bool]::Parse('#{allow_module_overwrite}') + CertificateID = #{certificate_id} + ConfigurationMode = '#{configuration_mode}' + ConfigurationModeFrequencyMins = #{configuration_mode_frequency_mins} + RebootNodeIfNeeded = [bool]::Parse('#{reboot_if_needed}') + RefreshFrequencyMins = #{refresh_frequency_mins} + RefreshMode = '#{refresh_mode}' + } + } + LCMSETUP + end + end + end + end +end diff --git a/lib/kitchen/provisioner/dsc_lcm/lcm_v4.rb b/lib/kitchen/provisioner/dsc_lcm/lcm_v4.rb new file mode 100644 index 0000000..0d07f39 --- /dev/null +++ b/lib/kitchen/provisioner/dsc_lcm/lcm_v4.rb @@ -0,0 +1,53 @@ +# -*- encoding: utf-8 -*- +# +# Author:: Steven Murawski () +# +# Copyright (C) 2014 Steven Murawski +# +# Licensed under the Apache 2 License. +# See LICENSE for more details + +require "kitchen/provisioner/dsc_lcm/lcm_base" + +module Kitchen + module Provisioner + module DscLcm + class LcmV4 < LcmBase + + def lcm_properties + { + :action_after_reboot => "StopConfiguration", + :allow_module_overwrite => false, + :certificate_id => nil, + :configuration_mode => "ApplyAndAutoCorrect", + :configuration_mode_frequency_mins => 30, + :debug_mode => "All", + :reboot_if_needed => false, + :refresh_mode => "PUSH", + :refresh_frequency_mins => 15 + } + end + + def lcm_configuration_script + <<-LCMSETUP + configuration SetupLCM + { + LocalConfigurationManager + { + ActionAfterReboot = '#{action_after_reboot}' + AllowModuleOverwrite = [bool]::Parse('#{allow_module_overwrite}') + CertificateID = #{certificate_id} + ConfigurationMode = '#{configuration_mode}' + ConfigurationModeFrequencyMins = #{configuration_mode_frequency_mins} + DebugMode = '#{debug_mode}' + RebootNodeIfNeeded = [bool]::Parse('#{reboot_if_needed}') + RefreshFrequencyMins = #{refresh_frequency_mins} + RefreshMode = '#{refresh_mode}' + } + } + LCMSETUP + end + end + end + end +end diff --git a/lib/kitchen/provisioner/dsc_lcm/lcm_v5.rb b/lib/kitchen/provisioner/dsc_lcm/lcm_v5.rb new file mode 100644 index 0000000..5febae3 --- /dev/null +++ b/lib/kitchen/provisioner/dsc_lcm/lcm_v5.rb @@ -0,0 +1,54 @@ +# -*- encoding: utf-8 -*- +# +# Author:: Steven Murawski () +# +# Copyright (C) 2014 Steven Murawski +# +# Licensed under the Apache 2 License. +# See LICENSE for more details + +require "kitchen/provisioner/dsc_lcm/lcm_base" + +module Kitchen + module Provisioner + module DscLcm + class LcmV5 < LcmBase + + def lcm_properties + { + :action_after_reboot => "StopConfiguration", + :allow_module_overwrite => false, + :certificate_id => nil, + :configuration_mode => "ApplyAndAutoCorrect", + :configuration_mode_frequency_mins => 15, + :debug_mode => "All", + :reboot_if_needed => false, + :refresh_mode => "PUSH", + :refresh_frequency_mins => 30 + } + end + + def lcm_configuration_script + <<-LCMSETUP + [DSCLocalConfigurationManager()] + configuration SetupLCM + { + Settings + { + ActionAfterReboot = '#{action_after_reboot}' + AllowModuleOverwrite = [bool]::Parse('#{allow_module_overwrite}') + CertificateID = #{certificate_id} + ConfigurationMode = '#{configuration_mode}' + ConfigurationModeFrequencyMins = #{configuration_mode_frequency_mins} + DebugMode = '#{debug_mode}' + RebootNodeIfNeeded = [bool]::Parse('#{reboot_if_needed}') + RefreshFrequencyMins = #{refresh_frequency_mins} + RefreshMode = '#{refresh_mode}' + } + } + LCMSETUP + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..7d72740 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,21 @@ +# -*- encoding: utf-8 -*- +# +# Author:: Fletcher Nichol () +# +# Copyright (C) 2012, Fletcher Nichol +# +# 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. + +gem "minitest" + +require "minitest/autorun"