summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2017-10-07 16:09:17 -0400
committerTim Smith <tsmith@chef.io>2017-12-13 15:38:29 -0800
commitdf8398534bd868c77d785e9f94a3a01bdeb0211f (patch)
tree1aec9c48952cd968a6100abbfbd306ad752dcde3
parentadfaadb7e203e5b8f24b2d7db20c3deae7cfa9eb (diff)
downloadchef-batteries_included.tar.gz
Add new resourcesbatteries_included
cpan_module dmg_package homebrew_cask homebrew_tap openssl_dhparam openssl_rsa_public_key openssl_rsa_private_key ohai_hint swap_file trusted_certificate windows_font windows_pagefile windows_printer windows_printer_port windows_share windows_shortcut Signed-off-by: Tim Smith <tsmith@chef.io>
-rw-r--r--lib/chef/mixin/openssl.rb40
-rw-r--r--lib/chef/resource/cpan_module.rb94
-rw-r--r--lib/chef/resource/dmg_package.rb104
-rw-r--r--lib/chef/resource/homebrew_cask.rb60
-rw-r--r--lib/chef/resource/homebrew_tap.rb63
-rw-r--r--lib/chef/resource/ohai_hint.rb83
-rw-r--r--lib/chef/resource/openssl_dhparam.rb72
-rw-r--r--lib/chef/resource/openssl_rsa_private_key.rb81
-rw-r--r--lib/chef/resource/openssl_rsa_public_key.rb54
-rw-r--r--lib/chef/resource/openssl_x509.rb146
-rw-r--r--lib/chef/resource/swap_file.rb200
-rw-r--r--lib/chef/resource/trusted_certificate.rb60
-rw-r--r--lib/chef/resource/windows_font.rb113
-rw-r--r--lib/chef/resource/windows_pagefile.rb164
-rw-r--r--lib/chef/resource/windows_path.rb9
-rw-r--r--lib/chef/resource/windows_printer.rb113
-rw-r--r--lib/chef/resource/windows_printer_port.rb110
-rw-r--r--lib/chef/resource/windows_share.rb299
-rw-r--r--lib/chef/resource/windows_shortcut.rb61
19 files changed, 1918 insertions, 8 deletions
diff --git a/lib/chef/mixin/openssl.rb b/lib/chef/mixin/openssl.rb
new file mode 100644
index 0000000000..a13e2c22af
--- /dev/null
+++ b/lib/chef/mixin/openssl.rb
@@ -0,0 +1,40 @@
+#
+# Copyright 2008-2017, Chef Software, Inc <legal@chef.io>
+#
+# 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.
+#
+# Ported from the homebrew cookbook's Homebrew::Mixin owner helpers
+#
+# This lives here in Chef::Mixin because Chef's namespacing makes it
+# awkward to use modules elsewhere (e.g., chef/provider/package/homebrew/owner)
+
+require "openssl"
+
+class Chef
+ module Mixin
+ module Openssl
+ # Validation helpers
+ def key_length_valid?(number)
+ number >= 1024 && number & (number - 1) == 0
+ end
+
+ def key_file_valid?(key_file_path, key_password = nil)
+ # Check if the key file exists
+ # Verify the key file contains a private key
+ return false unless ::File.exist?(key_file_path)
+ key = OpenSSL::PKey::RSA.new File.read(key_file_path), key_password
+ key.private?
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/cpan_module.rb b/lib/chef/resource/cpan_module.rb
new file mode 100644
index 0000000000..da63dc9a74
--- /dev/null
+++ b/lib/chef/resource/cpan_module.rb
@@ -0,0 +1,94 @@
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Joshua Timberman <jtimberman@chef.io>
+ # @author Steve Nolen <technolengy@gmail.com>
+ # resource to install modules from CPAN
+ class CpanModule < Chef::Resource
+ resource_name :cpan_module
+
+ property :module_name, String, name_property: true
+ property :cpam_bin_path, String, default: "/usr/local/bin/cpanm"
+ property :force, [true, false], default: false
+ property :test, [true, false], default: false
+ property :version, String
+ property :cwd, String
+
+ action :install do
+ declare_resource(:execute, "CPAN :install #{new_resource.module_name}") do
+ cwd current_working_dir
+ command cpanm_install_cmd
+ environment "HOME" => current_working_dir, "PATH" => "/usr/local/bin:/usr/bin:/bin"
+ not_if { module_exists_new_enough }
+ end
+ end
+
+ action :uninstall do
+ declare_resource(:execute, "CPAN :uninstall #{new_resource.module_name}") do
+ cwd current_working_dir
+ command cpanm_uninstall_cmd
+ only_if module_exists
+ end
+ end
+
+ action_class do
+ def module_exists_new_enough
+ existing_version = parse_cpan_version
+ return false if existing_version.empty? # mod doesn't exist
+ return true if new_resource.version.nil? # mod exists and version is unimportant
+ @comparator, @pending_version = new_resource.version.split(" ", 2)
+ @current_vers = Gem::Version.new(existing_version)
+ @pending_vers = Gem::Version.new(@pending_version)
+ @current_vers.method(@comparator).call(@pending_vers)
+ end
+
+ def parse_cpan_version
+ mod_ver_cmd = shell_out("perl -M#{new_resource.module_name} -e 'print $#{new_resource.module_name}::VERSION;' 2> /dev/null")
+ mod_ver = mod_ver_cmd.stdout
+ return mod_ver if mod_ver.empty?
+ # remove leading v and convert underscores to dots since gems parses them wrong
+ mod_ver.gsub!(/v_/, "v" => 3, "_" => ".")
+ # in the event that this command outputs whatever it feels like, only keep the first vers number!
+ version_match = /(^[0-9.]*)/.match(mod_ver)
+ version_match[0]
+ end
+
+ # builds a string of the perl command to see if the module exists
+ # @return [String]
+ def module_exists
+ "perl -m#{new_resource.module_name} -e ';' 2> /dev/null"
+ end
+
+ def cpanm_install_cmd
+ @cmd = "#{new_resource.cpam_bin_path} --quiet "
+ @cmd += "--force " if new_resource.force
+ @cmd += "--notest " unless new_resource.test
+ @cmd += new_resource.module_name
+ @cmd += parsed_version
+ @cmd
+ end
+
+ def cpanm_uninstall_cmd
+ @cmd = "#{new_resource.cpam_bin_path} "
+ @cmd += "--force " if new_resource.force
+ @cmd += "--uninstall "
+ @cmd += new_resource.module_name
+ @cmd
+ end
+
+ # a bit of a stub, could use a version parser for really consistent expeirence
+ def parsed_version
+ return "~\"#{new_resource.version}\"" if new_resource.version
+ ""
+ end
+
+ def current_working_dir
+ return new_resource.cwd if new_resource.cwd
+ return "/var/root" if node["platform"] == "mac_os_x"
+ "/root"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/dmg_package.rb b/lib/chef/resource/dmg_package.rb
new file mode 100644
index 0000000000..344b117f77
--- /dev/null
+++ b/lib/chef/resource/dmg_package.rb
@@ -0,0 +1,104 @@
+#
+# Copyright:: 2011-2017, Chef Software, Inc.
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Joshua Timberman <jtimberman@chef.io>
+ # resource to install OS X applications (.app) from dmg files.
+ class DmgPackage < Chef::Resource
+ resource_name :dmg_package
+ provides :dmg_package
+
+ property :app, String, name_property: true
+ property :source, String
+ property :file, String
+ property :owner, String
+ property :destination, String, default: "/Applications"
+ property :checksum, String
+ property :volumes_dir, String
+ property :dmg_name, String
+ property :type, String, default: "app"
+ property :installed, [true, false], default: false, desired_state: false
+ property :package_id, String
+ property :dmg_passphrase, String
+ property :accept_eula, [true, false], default: false
+ property :headers, [Hash, nil], default: nil
+
+ load_current_value do |new_resource|
+ if ::File.directory?("#{new_resource.destination}/#{new_resource.app}.app")
+ Chef::Log.info "Already installed; to upgrade, remove \"#{new_resource.destination}/#{new_resource.app}.app\""
+ installed true
+ elsif shell_out("pkgutil --pkgs='#{new_resource.package_id}'").exitstatus == 0
+ Chef::Log.info "Already installed; to upgrade, try \"sudo pkgutil --forget '#{new_resource.package_id}'\""
+ installed true
+ else
+ installed false
+ end
+ end
+
+ action :install do
+ unless current_resource.installed
+
+ volumes_dir = new_resource.volumes_dir ? new_resource.volumes_dir : new_resource.app
+ dmg_name = new_resource.dmg_name ? new_resource.dmg_name : new_resource.app
+ dmg_file = new_resource.file ? new_resource.file : "#{Chef::Config[:file_cache_path]}/#{dmg_name}.dmg"
+
+ if new_resource.source
+ declare_resource(:remote_file, "#{dmg_file} - #{new_resource.name}") do
+ path dmg_file
+ source new_resource.source
+ headers new_resource.headers if new_resource.headers
+ checksum new_resource.checksum if new_resource.checksum
+ end
+ end
+
+ passphrase_cmd = new_resource.dmg_passphrase ? "-passphrase #{new_resource.dmg_passphrase}" : ""
+ declare_resource(:ruby_block, "attach #{dmg_file}") do
+ block do
+ cmd = shell_out("hdiutil imageinfo #{passphrase_cmd} '#{dmg_file}' | grep -q 'Software License Agreement: true'")
+ software_license_agreement = cmd.exitstatus == 0
+ raise "Requires EULA Acceptance; add 'accept_eula true' to package resource" if software_license_agreement && !new_resource.accept_eula
+ accept_eula_cmd = new_resource.accept_eula ? "echo Y | PAGER=true" : ""
+ shell_out!("#{accept_eula_cmd} hdiutil attach #{passphrase_cmd} '#{dmg_file}' -mountpoint '/Volumes/#{volumes_dir}' -quiet")
+ end
+ not_if "hdiutil info #{passphrase_cmd} | grep -q 'image-path.*#{dmg_file}'"
+ end
+
+ case new_resource.type
+ when "app"
+ declare_resource(:execute, "rsync --force --recursive --links --perms --executability --owner --group --times '/Volumes/#{volumes_dir}/#{new_resource.app}.app' '#{new_resource.destination}'") do
+ user new_resource.owner if new_resource.owner
+ end
+
+ declare_resource(:file, "#{new_resource.destination}/#{new_resource.app}.app/Contents/MacOS/#{new_resource.app}") do
+ mode "755"
+ ignore_failure true
+ end
+ when "mpkg", "pkg"
+ declare_resource(:execute, "installation_file=$(ls '/Volumes/#{volumes_dir}' | grep '.#{new_resource.type}$') && sudo installer -pkg \"/Volumes/#{volumes_dir}/$installation_file\" -target /") do
+ # Prevent cfprefsd from holding up hdiutil detach for certain disk images
+ environment("__CFPREFERENCES_AVOID_DAEMON" => "1")
+ end
+ end
+
+ declare_resource(:execute, "hdiutil detach '/Volumes/#{volumes_dir}' || hdiutil detach '/Volumes/#{volumes_dir}' -force")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/homebrew_cask.rb b/lib/chef/resource/homebrew_cask.rb
new file mode 100644
index 0000000000..e84330bb41
--- /dev/null
+++ b/lib/chef/resource/homebrew_cask.rb
@@ -0,0 +1,60 @@
+#
+# Copyright:: 2011-2017, Chef Software, Inc.
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Graeme Mathieson <mathie@woss.name>
+ # @author Joshua Timberman <jtimberman@chef.io>
+ # Resource for brew cask, a Homebrew-style CLI workflow for the administration of Mac applications
+ # distributed as binaries. It's implemented as a homebrew "external command" called cask.
+ class HomebrewCask < Chef::Resource
+ resource_name :homebrew_cask
+
+ property :cask_name, String, regex: /^[\w-]+$/, name_property: true
+ property :options, String
+
+ action :install do
+ declare_resource(:execute, "installing cask #{new_resource.name}") do
+ command "/usr/local/bin/brew cask install #{new_resource.name} #{new_resource.options}"
+ user Homebrew.owner
+ environment lazy { { "HOME" => ::Dir.home(Homebrew.owner), "USER" => Homebrew.owner } }
+ not_if { casked? }
+ end
+ end
+
+ action :uninstall do
+ declare_resource(:execute, "uninstalling cask #{new_resource.name}") do
+ command "/usr/local/bin/brew cask uninstall #{new_resource.name}"
+ user Homebrew.owner
+ environment lazy { { "HOME" => ::Dir.home(Homebrew.owner), "USER" => Homebrew.owner } }
+ only_if { casked? }
+ end
+ end
+
+ action_class do
+ alias_method :action_cask, :action_install
+ alias_method :action_uncask, :action_uninstall
+
+ def casked?
+ shell_out("/usr/local/bin/brew cask list 2>/dev/null").stdout.split.include?(name)
+ shell_out("/usr/local/bin/brew cask list 2>/dev/null", user: Homebrew.owner).stdout.split.include?(name)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/homebrew_tap.rb b/lib/chef/resource/homebrew_tap.rb
new file mode 100644
index 0000000000..4232a0e157
--- /dev/null
+++ b/lib/chef/resource/homebrew_tap.rb
@@ -0,0 +1,63 @@
+#
+# Copyright:: 2011-2017, Chef Software, Inc.
+#
+# 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.
+#
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Graeme Mathieson <mathie@woss.name>
+ # @author Joshua Timberman <jtimberman@chef.io>
+ # Resouce for brew tap, a Homebrew command used to add additional formula repositories
+ class HomebrewTap < Chef::Resource
+ resource_name :homebrew_tap
+
+ property :tap_name,
+ String,
+ name_property: true,
+ regex: %r{^[\w-]+(?:\/[\w-]+)+$}
+ property :url, String
+ property :full, [TrueClass, FalseClass], default: false
+
+ action :tap do
+ unless tapped?(new_resource.name)
+ declare_resource(:execute, "tapping #{new_resource.name}") do
+ command "/usr/local/bin/brew tap #{full ? '--full' : ''} #{new_resource.name} #{url || ''}"
+ environment lazy { { "HOME" => ::Dir.home(Homebrew.owner), "USER" => Homebrew.owner } }
+ not_if "/usr/local/bin/brew tap | grep #{new_resource.name}"
+ user Homebrew.owner
+ end
+ end
+ end
+
+ action :untap do
+ if tapped?(new_resource.name)
+ declare_resource(:execute, "untapping #{new_resource.name}") do
+ command "/usr/local/bin/brew untap #{new_resource.name}"
+ environment lazy { { "HOME" => ::Dir.home(Homebrew.owner), "USER" => Homebrew.owner } }
+ only_if "/usr/local/bin/brew tap | grep #{new_resource.name}"
+ user Homebrew.owner
+ end
+ end
+ end
+
+ action_class do
+ def tapped?(name)
+ tap_dir = name.gsub("/", "/homebrew-")
+ ::File.directory?("/usr/local/Homebrew/Library/Taps/#{tap_dir}")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/ohai_hint.rb b/lib/chef/resource/ohai_hint.rb
new file mode 100644
index 0000000000..cf414dd736
--- /dev/null
+++ b/lib/chef/resource/ohai_hint.rb
@@ -0,0 +1,83 @@
+#
+# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Yukihiko SAWANOBORI <sawanoboriyu@gmail.com>
+ # creates Ohai hint files, which are consumed by Ohai plugins in order to determine if they should run or not.
+ class OhaiHint < Chef::Resource
+ resource_name :ohai_hint
+
+ property :hint_name, String, name_property: true
+ property :content, Hash
+ property :compile_time, [true, false], default: true
+
+ action :create do
+ declare_resource(:directory, ::Ohai::Config.ohai.hints_path.first) do
+ action :create
+ recursive true
+ end
+
+ declare_resource(:file ohai_hint_file_path(new_resource.hint_name)) do
+ action :create
+ content format_content(new_resource.content)
+ end
+ end
+
+ action :delete do
+ declare_resource(:file, ohai_hint_file_path(new_resource.hint_name)) do
+ action :delete
+ notifies :reload, ohai[reload ohai post hint removal]
+ end
+
+ declare_resource(:ohai, "reload ohai post hint removal") do
+ action :nothing
+ end
+ end
+
+ action_class do
+ def ohai_hint_file_path(filename)
+ path = ::File.join(::Ohai::Config.ohai.hints_path.first, filename)
+ path << ".json" unless path.end_with?(".json")
+ path
+ end
+
+ def format_content(content)
+ return "" if content.nil? || content.empty?
+ JSON.pretty_generate(content)
+ end
+
+ def file_content(path)
+ return JSON.parse(::File.read(path))
+ rescue JSON::ParserError
+ Chef::Log.debug("Could not parse JSON in ohai hint at #{ohai_hint_path}. It's probably an empty hint file")
+ return nil
+ end
+ end
+
+ # this resource forces itself to run at compile_time
+ def after_created
+ return unless compile_time
+ Array(action).each do |action|
+ run_action(action)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_dhparam.rb b/lib/chef/resource/openssl_dhparam.rb
new file mode 100644
index 0000000000..6260f83f82
--- /dev/null
+++ b/lib/chef/resource/openssl_dhparam.rb
@@ -0,0 +1,72 @@
+#
+# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Charles Johnson <charles@chef.io>
+ # generates dhparam.pem files. If a valid dhparam.pem file is found at the specified location,
+ # no new file will be created. If a file is found at the specified location but it is not a valid
+ # dhparam file, it will be overwritten.
+ class OpensslDhparam < Chef::Resource
+ require "openssl"
+
+ resource_name :openssl_dhparam
+
+ property :path, String, name_property: true
+ property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048
+ property :generator, equal_to: [2, 5], default: 2
+ property :owner, String, default: "root"
+ property :group, String, default: node["root_group"]
+ property :mode, [Integer, String], default: "0640"
+
+ action :create do
+ return if dhparam_pem_valid?(new_resource.path)
+
+ converge_by("Create a dhparam file #{new_resource.path}") do
+ dhparam_content = gen_dhparam(new_resource.key_length, new_resource.generator).to_pem
+
+ declare_resource(:file, new_resource.name) do
+ action :create
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode
+ sensitive true
+ content dhparam_content
+ end
+ end
+ end
+
+ action_class do
+ # Check if the dhparam.pem file exists
+ # Verify the dhparam.pem file contains a key
+ def dhparam_pem_valid?(dhparam_pem_path)
+ return false unless ::File.exist?(dhparam_pem_path)
+ dhparam = OpenSSL::PKey::DH.new File.read(dhparam_pem_path)
+ dhparam.params_ok?
+ end
+
+ # generate a dhparam file
+ def gen_dhparam(key_length, generator)
+ raise ArgumentError, "Key length must be a power of 2 greater than or equal to 1024" unless key_length_valid?(key_length)
+ OpenSSL::PKey::DH.new(key_length, generator)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_rsa_private_key.rb b/lib/chef/resource/openssl_rsa_private_key.rb
new file mode 100644
index 0000000000..049a52e1a1
--- /dev/null
+++ b/lib/chef/resource/openssl_rsa_private_key.rb
@@ -0,0 +1,81 @@
+#
+# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Charles Johnson <charles@chef.io>
+ # generates rsa private key files. If a valid rsa key file can be opened at the specified location,
+ # no new file will be created. If the RSA key file cannot be opened, either because it does not exist
+ # or because the password to the RSA key file does not match the password in the recipe, it will be overwritten.
+ class OpensslRsaPrivateKey < Chef::Resource
+ require "openssl"
+
+ resource_name :openssl_rsa_private_key
+
+ property :path, String, name_property: true
+ property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048
+ property :key_pass, String
+ property :key_cipher, String, default: "des3", equal_to: valid_ciphers
+ property :owner, String, default: "root"
+ property :group, String, default: node["root_group"]
+ property :mode, [Integer, String], default: "0640"
+ property :force, [true, false], default: false
+
+ action :create do
+ return if new_resource.force || priv_key_file_valid?(new_resource.path, new_resource.key_pass)
+
+ converge_by("create #{new_resource.key_length} bit RSA key #{new_resource.path}") do
+ if new_resource.key_pass
+ unencrypted_rsa_key = gen_rsa_priv_key(new_resource.key_length)
+ rsa_key_content = encrypt_rsa_key(unencrypted_rsa_key, new_resource.key_pass, new_resource.cipher)
+ else
+ rsa_key_content = gen_rsa_priv_key(new_resource.key_length).to_pem
+ end
+
+ declare_resource(:file, new_resource.path) do
+ action :create
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode
+ sensitive true
+ content rsa_key_content
+ end
+ end
+ end
+
+ action_class do
+ def gen_rsa_key(key_length)
+ raise ArgumentError, "Key length must be a power of 2 greater than or equal to 1024" unless key_length_valid?(key_length)
+
+ OpenSSL::PKey::RSA.new(key_length)
+ end
+
+ # Key manipulation helpers
+ # Returns a pem string
+ def encrypt_rsa_key(rsa_key, key_password)
+ raise TypeError, "rsa_key must be a Ruby OpenSSL::PKey::RSA object" unless rsa_key.is_a?(OpenSSL::PKey::RSA)
+ raise TypeError, "RSA key password must be a string" unless key_password.is_a?(String)
+
+ cipher = OpenSSL::Cipher::Cipher.new("des3")
+ rsa_key.to_pem(cipher, key_password)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_rsa_public_key.rb b/lib/chef/resource/openssl_rsa_public_key.rb
new file mode 100644
index 0000000000..31899cbd4a
--- /dev/null
+++ b/lib/chef/resource/openssl_rsa_public_key.rb
@@ -0,0 +1,54 @@
+#
+# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Tim Smith <tsmith@chef.io>
+ # generates rsa public key files given a private key.
+ class OpensslRsaPublicKey < Chef::Resource
+ require "openssl"
+
+ resource_name :openssl_rsa_public_key
+
+ property :path, String, name_property: true
+ property :private_key_path, String
+ property :private_key_content, String
+ property :private_key_pass, String
+ property :owner, String, default: 'root'
+ property :group, String, default: node['root_group']
+ property :mode, [Integer, String], default: '0640'
+
+ action :create do
+ raise ArgumentError, "You cannot specify both 'private_key_path' and 'private_key_content' properties at the same time." if new_resource.private_key_path && new_resource.private_key_content
+ raise ArgumentError, "You must specify the private key with either 'private_key_path' or 'private_key_content' properties." unless new_resource.private_key_path || new_resource.private_key_content
+ raise "#{new_resource.private_key_path} not a valid private RSA key or password is invalid" unless priv_key_file_valid?((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ rsa_key_content = gen_rsa_pub_key((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ file new_resource.path do
+ action :create
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode
+ content rsa_key_content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_x509.rb b/lib/chef/resource/openssl_x509.rb
new file mode 100644
index 0000000000..1e74e4306f
--- /dev/null
+++ b/lib/chef/resource/openssl_x509.rb
@@ -0,0 +1,146 @@
+#
+# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Jesse Nelson <spheromak@gmail.com>
+ # generates self-signed, PEM-formatted x509 certificates
+ class OpensslX509 < Chef::Resource
+ require "openssl"
+
+ resource_name :openssl_x509
+
+ property :owner, String
+ property :group, String
+ property :expire, Integer
+ property :mode, [Integer, String]
+ property :org, String, required: true
+ property :org_unit, String, required: true
+ property :country, String, required: true
+ property :common_name, String, required: true
+ property :subject_alt_name, Array, default: []
+ property :key_file, String
+ property :key_pass, String
+ property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048
+
+ action :create do
+ return if ::File.exist? new_resource.name
+
+ converge_by("create #{new_resource.key_length} bit #{new_resource.name} x509 cert") do
+ create_keys
+ cert_content = cert.to_pem
+ key_content = key.to_pem
+
+ declare_resource(:file, new_resource.name) do
+ action :create_if_missing
+ mode new_resource.mode
+ owner new_resource.owner
+ group new_resource.group
+ sensitive true
+ content cert_content
+ end
+
+ declare_resource(:file, new_resource.key_file) do
+ action :create_if_missing
+ mode new_resource.mode
+ owner new_resource.owner
+ group new_resource.group
+ sensitive true
+ content key_content
+ end
+ end
+ end
+
+ action_class do
+ def generate_key_file
+ unless new_resource.key_file
+ path, file = ::File.split(new_resource.name)
+ filename = ::File.basename(file, ::File.extname(file))
+ new_resource.key_file path + "/" + filename + ".key"
+ end
+ new_resource.key_file
+ end
+
+ def key
+ @key ||= if key_file_valid?(generate_key_file, new_resource.key_pass)
+ OpenSSL::PKey::RSA.new ::File.read(generate_key_file), new_resource.key_pass
+ else
+ OpenSSL::PKey::RSA.new(new_resource.key_length)
+ end
+ @key
+ end
+
+ def cert
+ @cert ||= OpenSSL::X509::Certificate.new
+ end
+
+ def gen_cert
+ cert
+ cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
+ cert.not_before = Time.now
+ cert.not_after = Time.now + (new_resource.expire.to_i * 24 * 60 * 60)
+ cert.public_key = key.public_key
+ cert.serial = 0x0
+ cert.version = 2
+ end
+
+ def subject
+ @subject ||= "/C=" + new_resource.country +
+ "/O=" + new_resource.org +
+ "/OU=" + new_resource.org_unit +
+ "/CN=" + new_resource.common_name
+ end
+
+ def extensions
+ exts = []
+ exts << @ef.create_extension("basicConstraints", "CA:TRUE", true)
+ exts << @ef.create_extension("subjectKeyIdentifier", "hash")
+
+ unless new_resource.subject_alt_name.empty?
+ san = {}
+ counters = {}
+ new_resource.subject_alt_name.each do |an|
+ kind, value = an.split(":", 2)
+ counters[kind] ||= 0
+ counters[kind] += 1
+ san["#{kind}.#{counters[kind]}"] = value
+ end
+ @ef.config["alt_names"] = san
+ exts << @ef.create_extension("subjectAltName", "@alt_names")
+ end
+
+ exts
+ end
+
+ def create_keys
+ gen_cert
+ @ef ||= OpenSSL::X509::ExtensionFactory.new
+ @ef.subject_certificate = cert
+ @ef.issuer_certificate = cert
+ @ef.config = OpenSSL::Config.load(OpenSSL::Config::DEFAULT_CONFIG_FILE)
+
+ cert.extensions = extensions
+ cert.add_extension @ef.create_extension("authorityKeyIdentifier",
+ "keyid:always,issuer:always")
+ cert.sign key, OpenSSL::Digest::SHA256.new
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/swap_file.rb b/lib/chef/resource/swap_file.rb
new file mode 100644
index 0000000000..991c6cdfb7
--- /dev/null
+++ b/lib/chef/resource/swap_file.rb
@@ -0,0 +1,200 @@
+#
+# Copyright 2012-2014, Seth Vargo
+# Copyright 2016-2017, Chef Software, Inc.
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Seth Vargo <seth@sethvargo.com>
+ # @author Tim Smith <tsmith@chef.io>
+ # create and manage swap files
+ class SwapFile < Chef::Resource
+ resource_name :swap_file
+
+ property :path, String, name_attribute: true
+ property :size, Integer
+ property :persist, [TrueClass, FalseClass], default: false
+ property :timeout, Integer, default: 600
+ property :swappiness, Integer
+
+ action :create do
+ if swap_enabled?
+ Chef::Log.debug("#{new_resource} already created - nothing to do")
+ else
+ begin
+ Chef::Log.info "starting first create: #{node['virtualization']['system']}"
+ do_create(swap_creation_command)
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
+ Chef::Log.warn("#{new_resource} Rescuing failed swapfile creation for #{new_resource.path}")
+ Chef::Log.debug("#{new_resource} Exception when creating swapfile #{new_resource.path}: #{e}")
+ do_create(dd_command)
+ end
+ end
+ if new_resource.swappiness
+ include_recipe "sysctl::default"
+
+ sysctl_param "vm.swappiness" do
+ value new_resource.swappiness
+ end
+ end
+ end
+
+ action :remove do
+ swapoff if swap_enabled?
+ remove_swapfile if ::File.exist?(new_resource.path)
+ end
+
+ action_class do
+ def do_create(command)
+ create_swapfile(command)
+ set_permissions
+ mkswap
+ swapon
+ persist if persist?
+ end
+
+ def create_swapfile(command)
+ converge_by "create empty swapfile at #{new_resource.path}" do # ~FC054
+ shell_out!(command, timeout: new_resource.timeout)
+ end
+ end
+
+ def set_permissions
+ permissions = "600"
+ converge_by "set permissions on #{new_resource.path} to #{permissions}" do
+ shell_out!("chmod #{permissions} #{new_resource.path}")
+ end
+ end
+
+ def mkswap
+ converge_by "make #{new_resource.path} swappable" do
+ shell_out!("mkswap -f #{new_resource.path}")
+ end
+ end
+
+ def swapon
+ converge_by "enable swap for #{new_resource.path}" do
+ shell_out!("swapon #{new_resource.path}")
+ end
+ end
+
+ def swapoff
+ converge_by "turn off swap for #{new_resource.path}" do
+ shell_out!("swapoff #{new_resource.path}")
+ end
+ end
+
+ def remove_swapfile
+ converge_by "remove swap file #{new_resource.path}" do
+ ::FileUtils.rm(new_resource.path)
+ end
+ end
+
+ def swap_enabled?
+ enabled_swapfiles = shell_out("swapon --summary").stdout
+ # Regex for our resource path and only our resource path
+ # It will terminate on whitespace after the path it match
+ # /testswapfile would match
+ # /testswapfiledir/someotherfile will not
+ swapfile_regex = Regexp.new("^#{new_resource.path}[\\s\\t\\n\\f]+")
+ !swapfile_regex.match(enabled_swapfiles).nil?
+ end
+
+ def swap_creation_command
+ command = if compatible_filesystem? && compatible_kernel && !docker?
+ fallocate_command
+ else
+ dd_command
+ end
+ Chef::Log.debug("#{new_resource} swap creation command is '#{command}'")
+ command
+ end
+
+ def fallback_swap_creation_command
+ command = dd_command
+ Chef::Log.debug("#{new_resource} fallback swap creation command is '#{command}'")
+ command
+ end
+
+ # The block size (1MB)
+ def block_size
+ 1_048_576
+ end
+
+ def fallocate_size
+ size = block_size * new_resource.size
+ Chef::Log.debug("#{new_resource} fallocate size is #{size}")
+ size
+ end
+
+ def fallocate_command
+ size = fallocate_size
+ command = "fallocate -l #{size} #{new_resource.path}"
+ Chef::Log.debug("#{new_resource} fallocate command is '#{command}'")
+ command
+ end
+
+ def dd_command
+ command = "dd if=/dev/zero of=#{new_resource.path} bs=#{block_size} count=#{new_resource.size}"
+ Chef::Log.debug("#{new_resource} dd command is '#{command}'")
+ command
+ end
+
+ def compatible_kernel
+ fallocate_location = shell_out("which fallocate").stdout
+ Chef::Log.debug("#{new_resource} fallocate location is '#{fallocate_location}'")
+ ::File.exist?(fallocate_location.chomp)
+ end
+
+ def compatible_filesystem?
+ compatible_filesystems = %w{xfs ext4}
+ parent_directory = ::File.dirname(new_resource.path)
+ # Get FS info, get second line as first is column headings
+ command = "df -PT #{parent_directory} | awk 'NR==2 {print $2}'"
+ result = shell_out(command).stdout
+ Chef::Log.debug("#{new_resource} filesystem listing is '#{result}'")
+ compatible_filesystems.any? { |fs| result.include? fs }
+ end
+
+ # we can remove this when we only support Chef 13
+ def docker?(node = run_context.nil? ? nil : run_context.node)
+ !!(node && node["virtualization"] && node["virtualization"]["systems"] &&
+ node["virtualization"]["systems"]["docker"] && node["virtualization"]["systems"]["docker"] == "guest")
+ end
+
+ def persist?
+ !!new_resource.persist
+ end
+
+ def persist
+ fstab = "/etc/fstab"
+ contents = ::File.readlines(fstab)
+ addition = "#{new_resource.path} swap swap defaults 0 0"
+
+ if contents.any? { |line| line.strip == addition }
+ Chef::Log.debug("#{new_resource} already added to /etc/fstab - skipping")
+ else
+ Chef::Log.info("#{new_resource} adding entry to #{fstab} for #{new_resource.path}")
+
+ contents << "#{addition}\n"
+ ::File.open(fstab, "w") { |f| f.write(contents.join("")) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/trusted_certificate.rb b/lib/chef/resource/trusted_certificate.rb
new file mode 100644
index 0000000000..9f3908f899
--- /dev/null
+++ b/lib/chef/resource/trusted_certificate.rb
@@ -0,0 +1,60 @@
+#
+# Copyright:: 2015-2017, Chef Software, Inc.
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Tom Duffield <github@tomduffield.com>
+ # resource to manage adding SSL/TLS certificates to the operating system's trust store
+ class TrustedCertificate < Chef::Resource
+ resource_name :trusted_certificate
+
+ property :certificate_name, String, name_property: true
+ property :content, String, required: true
+
+ provides :trusted_certificate
+
+ action :create do
+ declare_resource(:execute, "update trusted certificates") do
+ command platform_family?("debian", "suse") ? "update-ca-certificates" : "update-ca-trust extract"
+ action :nothing
+ end
+
+ declare_resource(:file, "#{certificate_path}/#{new_resource.certificate_name}.crt") do
+ content new_resource.content
+ owner "root"
+ group "staff" if platform_family?("debian")
+ action :create
+ notifies :run, "execute[update trusted certificates]"
+ end
+ end
+
+ action_class do
+ def certificate_path
+ case node["platform_family"]
+ when "debian"
+ "/usr/local/share/ca-certificates"
+ when "suse"
+ "/etc/pki/trust/anchors/"
+ else # probably RHEL
+ "/etc/pki/ca-trust/source/anchors"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_font.rb b/lib/chef/resource/windows_font.rb
new file mode 100644
index 0000000000..fbcbbecfbb
--- /dev/null
+++ b/lib/chef/resource/windows_font.rb
@@ -0,0 +1,113 @@
+#
+# Copyright:: 2014-2017, Schuberg Philis BV.
+# Copyright:: 2017, Chef Software, Inc.
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Sander Botman <sbotman@schubergphilis.com>
+ # Installs font files. Sources the font by default from the cookbook, but a URI source can be specified as well.
+ class WindowsFont < Chef::Resource
+ resource_name :windows_font
+ provides :windows_font, os: "windows"
+
+ property :font_name, String, name_property: true
+ property :source, String, coerce: proc { |x| x.tr('\\', "/").gsub("//", "/") }
+
+ include Windows::Helper
+
+ action :install do
+ if font_exists?
+ Chef::Log.debug("Not installing font: #{new_resource.font_name} as font already installed.")
+ else
+ retrieve_cookbook_font
+ install_font
+ del_cookbook_font
+ end
+ end
+
+ action_class do
+ # if a source is specified fetch using remote_file. It not use cookbook_file
+ def retrieve_cookbook_font
+ font_file = new_resource.font_name
+ if new_resource.source
+ declare_resource(:remote_file, font_file) do
+ action :nothing
+ source source_uri
+ path win_friendly_path(::File.join(ENV["TEMP"], font_file))
+ end.run_action(:create)
+ else
+ declare_resource(:cookbook_file, font_file) do
+ action :nothing
+ cookbook cookbook_name.to_s unless cookbook_name.nil?
+ path win_friendly_path(::File.join(ENV["TEMP"], font_file))
+ end.run_action(:create)
+ end
+ end
+
+ # delete the temp cookbook file
+ def del_cookbook_font
+ declare_resource(:file, ::File.join(ENV["TEMP"], new_resource.font_name)) do
+ action :delete
+ end
+ end
+
+ # install the font into the appropriate fonts directory
+ def install_font
+ require "win32ole" if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ fonts_dir = WIN32OLE.new("WScript.Shell").SpecialFolders("Fonts")
+ folder = WIN32OLE.new("Shell.Application").Namespace(fonts_dir)
+ converge_by("install font #{new_resource.font_name} to #{fonts_dir}") do
+ folder.CopyHere(win_friendly_path(::File.join(ENV["TEMP"], new_resource.font_name)))
+ end
+ end
+
+ # Check to see if the font is installed in the fonts dir
+ #
+ # @return [Boolean] Is the font is installed?
+ def font_exists?
+ require "win32ole" if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ fonts_dir = WIN32OLE.new("WScript.Shell").SpecialFolders("Fonts")
+ Chef::Log.debug("Seeing if the font at #{win_friendly_path(::File.join(fonts_dir, new_resource.font_name))} exists")
+ ::File.exist?(win_friendly_path(::File.join(fonts_dir, new_resource.font_name)))
+ end
+
+ # Parse out the schema provided to us to see if it's one we support via remote_file.
+ # We do this because URI will parse C:/foo as schema 'c', which won't work with remote_file
+ def remote_file_schema?(schema)
+ return true if %w{http https ftp}.include?(schema)
+ end
+
+ # return new_resource.source if we have a proper URI specified
+ # if it's a local file listed as a source return it in file:// format
+ def source_uri
+ begin
+ require "uri"
+ if remote_file_schema?(URI.parse(new_resource.source).scheme)
+ Chef::Log.debug("source property starts with ftp/http. Using source property unmodified")
+ return new_resource.source
+ end
+ rescue URI::InvalidURIError
+ Chef::Log.warn("source property of #{new_resource.source} could not be processed as a URI. Check the format you provided.")
+ end
+ Chef::Log.debug("source property does not start with ftp/http. Prepending with file:// as it appears to be a local file.")
+ "file://#{new_resource.source}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_pagefile.rb b/lib/chef/resource/windows_pagefile.rb
new file mode 100644
index 0000000000..f657384234
--- /dev/null
+++ b/lib/chef/resource/windows_pagefile.rb
@@ -0,0 +1,164 @@
+#
+#
+#
+# Copyright:: 2012-2017, Nordstrom, Inc.
+# Copyright:: 2017, Chef Software, Inc.
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ # @author Kevin Moser <kevin.moser@nordstrom.com>
+ # Configures the file that provides virtual memory for applications requiring
+ # more memory than available RAM or that are paged out to free up memory in use.
+ class WindowsPagefile < Chef::Resource
+
+ property :name, String, name_property: true
+ property :system_managed, [true, false]
+ property :automatic_managed, [true, false], default: false
+ property :initial_size, Integer
+ property :maximum_size, Integer
+
+ include Chef::Mixin::ShellOut
+ include Windows::Helper
+
+ action :set do
+ pagefile = new_resource.name
+ initial_size = new_resource.initial_size
+ maximum_size = new_resource.maximum_size
+ system_managed = new_resource.system_managed
+ automatic_managed = new_resource.automatic_managed
+
+ if automatic_managed
+ set_automatic_managed unless automatic_managed?
+ else
+ unset_automatic_managed if automatic_managed?
+
+ # Check that the resource is not just trying to unset automatic managed, if it is do nothing more
+ if (initial_size && maximum_size) || system_managed
+ validate_name
+ create(pagefile) unless exists?(pagefile)
+
+ if system_managed
+ set_system_managed(pagefile) unless max_and_min_set?(pagefile, 0, 0)
+ else
+ unless max_and_min_set?(pagefile, initial_size, maximum_size)
+ set_custom_size(pagefile, initial_size, maximum_size)
+ end
+ end
+ end
+ end
+ end
+
+ action :delete do
+ validate_name
+ pagefile = new_resource.name
+ delete(pagefile) if exists?(pagefile)
+ end
+
+ action_class do
+ def validate_name
+ return if /^.:.*.sys/ =~ new_resource.name
+ raise "#{new_resource.name} does not match the format DRIVE:\\path\\file.sys for pagefiles. Example: C:\\pagefile.sys"
+ end
+
+ def exists?(pagefile)
+ @exists ||= begin
+ Chef::Log.debug("Checking if #{pagefile} exists by runing: #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list")
+ cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", returns: [0])
+ cmd.stderr.empty? && (cmd.stdout =~ /SettingID=#{get_setting_id(pagefile)}/i)
+ end
+ end
+
+ def max_and_min_set?(pagefile, min, max)
+ @max_and_min_set ||= begin
+ Chef::Log.debug("Checking if #{pagefile} min: #{min} and max #{max} are set")
+ cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", returns: [0])
+ cmd.stderr.empty? && (cmd.stdout =~ /InitialSize=#{min}/i) && (cmd.stdout =~ /MaximumSize=#{max}/i)
+ end
+ end
+
+ def create(pagefile)
+ converge_by("create pagefile #{pagefile}") do
+ Chef::Log.debug("Running #{wmic} pagefileset create name=\"#{win_friendly_path(pagefile)}\"")
+ cmd = shell_out("#{wmic} pagefileset create name=\"#{win_friendly_path(pagefile)}\"")
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ def delete(pagefile)
+ converge_by("remove pagefile #{pagefile}") do
+ Chef::Log.debug("Running #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete")
+ cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete")
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ def automatic_managed?
+ @automatic_managed ||= begin
+ Chef::Log.debug("Checking if pagefiles are automatically managed")
+ cmd = shell_out("#{wmic} computersystem where name=\"%computername%\" get AutomaticManagedPagefile /format:list")
+ cmd.stderr.empty? && (cmd.stdout =~ /AutomaticManagedPagefile=TRUE/i)
+ end
+ end
+
+ def set_automatic_managed
+ converge_by("set pagefile to Automatic Managed") do
+ Chef::Log.debug("Running #{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True")
+ cmd = shell_out("#{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True")
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ def unset_automatic_managed
+ converge_by("set pagefile to User Managed") do
+ Chef::Log.debug("Running #{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False")
+ cmd = shell_out("#{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False")
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ def set_custom_size(pagefile, min, max)
+ converge_by("set #{pagefile} to InitialSize=#{min} & MaximumSize=#{max}") do
+ Chef::Log.debug("Running #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}")
+ cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}", returns: [0])
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ def set_system_managed(pagefile) # rubocop: disable Style/AccessorMethodName
+ converge_by("set #{pagefile} to System Managed") do
+ Chef::Log.debug("Running #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0")
+ cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0", returns: [0])
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ def get_setting_id(pagefile)
+ pagefile = win_friendly_path(pagefile)
+ pagefile = pagefile.split('\\')
+ "#{pagefile[1]} @ #{pagefile[0]}"
+ end
+
+ def check_for_errors(stderr)
+ raise stderr.chomp unless stderr.empty?
+ end
+
+ def wmic
+ @wmic ||= locate_sysnative_cmd("wmic.exe")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_path.rb b/lib/chef/resource/windows_path.rb
index 5472a7e4fd..c09b34a0c9 100644
--- a/lib/chef/resource/windows_path.rb
+++ b/lib/chef/resource/windows_path.rb
@@ -22,19 +22,12 @@ class Chef
class Resource
class WindowsPath < Chef::Resource
+ resource_name :windows_path
provides :windows_path, os: "windows"
allowed_actions :add, :remove
default_action :add
- def initialize(name, run_context = nil)
- super
- @resource_name = :windows_path
- @path = name
- @provider = Chef::Provider::WindowsPath
- @action = :add
- end
-
property :path, String, name_property: true
end
end
diff --git a/lib/chef/resource/windows_printer.rb b/lib/chef/resource/windows_printer.rb
new file mode 100644
index 0000000000..669ffe2e18
--- /dev/null
+++ b/lib/chef/resource/windows_printer.rb
@@ -0,0 +1,113 @@
+#
+# Copyright:: 2012-2017, Nordstrom, Inc.
+#
+# 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.
+#
+# See here for more info:
+# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394492(v=vs.85).aspx
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Doug Ireton <doug@1strategy.com>
+ # Create Windows printer. Note that this doesn't currently install a printer driver.
+ # You must already have the driver installed on the system.
+ class WindowsPrinter < Chef::Resource
+ resource_name :windows_printer
+ provides :windows_printer, os: "windows"
+
+ require "resolv"
+
+ property :device_id, String, name_property: true, required: true
+ property :comment, String
+ property :default, [true, false], default: false
+ property :driver_name, String, required: true
+ property :location, String
+ property :shared, [true, false], default: false
+ property :share_name, String
+ property :ipv4_address, String, regex: Resolv::IPv4::Regex
+ property :exists, [true, false], desired_state: true
+
+ PRINTERS_REG_KEY = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\\'.freeze unless defined?(PRINTERS_REG_KEY)
+
+ def printer_exists?(name)
+ printer_reg_key = PRINTERS_REG_KEY + name
+ Chef::Log.debug "Checking to see if this reg key exists: '#{printer_reg_key}'"
+ Registry.key_exists?(printer_reg_key)
+ end
+
+ load_current_value do |desired|
+ name desired.name
+ exists printer_exists?(desired.name)
+ # TODO: Set @current_resource printer properties from registry
+ end
+
+ action :create do
+ if @current_resource.exists
+ Chef::Log.info "#{@new_resource} already exists - nothing to do."
+ else
+ converge_by("Create #{@new_resource}") do
+ create_printer
+ end
+ end
+ end
+
+ action :delete do
+ if @current_resource.exists
+ converge_by("Delete #{@new_resource}") do
+ delete_printer
+ end
+ else
+ Chef::Log.info "#{@current_resource} doesn't exist - can't delete."
+ end
+ end
+
+ action_class do
+ def create_printer
+ # Create the printer port first
+ windows_printer_port new_resource.ipv4_address do
+ end
+
+ port_name = "IP_#{new_resource.ipv4_address}"
+
+ declare_resource(:powershell_script, "Creating printer: #{new_resource.name}") do
+ code <<-EOH
+
+ Set-WmiInstance -class Win32_Printer `
+ -EnableAllPrivileges `
+ -Argument @{ DeviceID = "#{new_resource.device_id}";
+ Comment = "#{new_resource.comment}";
+ Default = "$#{new_resource.default}";
+ DriverName = "#{new_resource.driver_name}";
+ Location = "#{new_resource.location}";
+ PortName = "#{port_name}";
+ Shared = "$#{new_resource.shared}";
+ ShareName = "#{new_resource.share_name}";
+ }
+ EOH
+ end
+ end
+
+ def delete_printer
+ declare_resource(:powershell_script, "Deleting printer: #{new_resource.name}") do
+ code <<-EOH
+ $printer = Get-WMIObject -class Win32_Printer -EnableAllPrivileges -Filter "name = '#{new_resource.name}'"
+ $printer.Delete()
+ EOH
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_printer_port.rb b/lib/chef/resource/windows_printer_port.rb
new file mode 100644
index 0000000000..70aea8c30b
--- /dev/null
+++ b/lib/chef/resource/windows_printer_port.rb
@@ -0,0 +1,110 @@
+#
+# Copyright:: 2012-2017, Nordstrom, Inc.
+#
+# 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.
+#
+# See here for more info:
+# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394492(v=vs.85).aspx
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Doug Ireton <doug@1strategy.com>
+ # Create and delete TCP/IPv4 printer ports.
+ class WindowsPrinterPort < Chef::Resource
+ resource_name :windows_printer_port
+ provides :windows_printer_port, os: "windows"
+
+ require "resolv"
+
+ property :ipv4_address, String, name_property: true, required: true, regex: Resolv::IPv4::Regex
+ property :port_name, String
+ property :port_number, Integer, default: 9100
+ property :port_description, String
+ property :snmp_enabled, [true, false], default: false
+ property :port_protocol, Integer, default: 1, equal_to: [1, 2]
+ property :exists, [true, false], desired_state: true
+
+ PORTS_REG_KEY = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\\'.freeze unless defined?(PORTS_REG_KEY)
+
+ def port_exists?(name)
+ port_reg_key = PORTS_REG_KEY + name
+
+ Chef::Log.debug "Checking to see if this reg key exists: '#{port_reg_key}'"
+ Registry.key_exists?(port_reg_key)
+ end
+
+ load_current_value do |desired|
+ name desired.name
+ ipv4_address desired.ipv4_address
+ port_name desired.port_name || "IP_#{@new_resource.ipv4_address}"
+ exists port_exists?(desired.port_name)
+ # TODO: Set @current_resource port properties from registry
+ end
+
+ action :create do
+ if current_resource.exists
+ Chef::Log.info "#{@new_resource} already exists - nothing to do."
+ else
+ converge_by("Create #{@new_resource}") do
+ create_printer_port
+ end
+ end
+ end
+
+ action :delete do
+ if current_resource.exists
+ converge_by("Delete #{@new_resource}") do
+ delete_printer_port
+ end
+ else
+ Chef::Log.info "#{@current_resource} doesn't exist - can't delete."
+ end
+ end
+
+ action_class do
+ def create_printer_port
+ port_name = new_resource.port_name || "IP_#{new_resource.ipv4_address}"
+
+ # create the printer port using PowerShell
+ declare_resource(:powershell_script, "Creating printer port #{new_resource.port_name}") do
+ code <<-EOH
+
+ Set-WmiInstance -class Win32_TCPIPPrinterPort `
+ -EnableAllPrivileges `
+ -Argument @{ HostAddress = "#{new_resource.ipv4_address}";
+ Name = "#{port_name}";
+ Description = "#{new_resource.port_description}";
+ PortNumber = "#{new_resource.port_number}";
+ Protocol = "#{new_resource.port_protocol}";
+ SNMPEnabled = "$#{new_resource.snmp_enabled}";
+ }
+ EOH
+ end
+ end
+
+ def delete_printer_port
+ port_name = new_resource.port_name || "IP_#{new_resource.ipv4_address}"
+
+ declare_resource(:powershell_script, "Deleting printer port: #{new_resource.port_name}") do
+ code <<-EOH
+ $port = Get-WMIObject -class Win32_TCPIPPrinterPort -EnableAllPrivileges -Filter "name = '#{port_name}'"
+ $port.Delete()
+ EOH
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_share.rb b/lib/chef/resource/windows_share.rb
new file mode 100644
index 0000000000..ee75041012
--- /dev/null
+++ b/lib/chef/resource/windows_share.rb
@@ -0,0 +1,299 @@
+#
+# Copyright:: 2014-2017, Sölvi Páll Ásgeirsson.
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Sölvi Páll Ásgeirsson <solvip@gmail.com>
+ # @author Richard Lavey <richard.lavey@calastone.com>
+ class WindowsShare < Chef::Resource
+ resource_name :windows_share
+ provides :windows_share, os: "windows"
+
+ property :share_name, String, name_property: true
+ property :path, String
+ property :description, String, default: ""
+ property :full_users, Array, default: []
+ property :change_users, Array, default: []
+ property :read_users, Array, default: []
+
+ include Windows::Helper
+ include Chef::Mixin::PowershellOut
+
+ require "win32ole" if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+
+ ACCESS_FULL = 2_032_127
+ ACCESS_CHANGE = 1_245_631
+ ACCESS_READ = 1_179_817
+
+ action :create do
+ raise "No path property set" unless new_resource.path
+
+ if different_path?
+ unless current_resource.path.nil? || current_resource.path.empty?
+ converge_by("Removing previous share") do
+ delete_share
+ end
+ end
+ converge_by("Creating share #{current_resource.share_name}") do
+ create_share
+ end
+ end
+
+ if different_members?(:full_users) ||
+ different_members?(:change_users) ||
+ different_members?(:read_users) ||
+ different_description?
+ converge_by("Setting permissions and description for #{new_resource.share_name}") do
+ set_share_permissions
+ end
+ end
+ end
+
+ action :delete do
+ if !current_resource.path.nil? && !current_resource.path.empty?
+ converge_by("Deleting #{current_resource.share_name}") do
+ delete_share
+ end
+ else
+ Chef::Log.debug("#{current_resource.share_name} does not exist - nothing to do")
+ end
+ end
+
+ load_current_value do |desired|
+ wmi = WIN32OLE.connect("winmgmts://")
+ shares = wmi.ExecQuery("SELECT * FROM Win32_Share WHERE name = '#{desired.share_name}'")
+ existing_share = shares.Count == 0 ? nil : shares.ItemIndex(0)
+
+ description ""
+ unless existing_share.nil?
+ path existing_share.Path
+ description existing_share.Description
+ end
+
+ perms = share_permissions name
+ unless perms.nil?
+ full_users perms[:full_users]
+ change_users perms[:change_users]
+ read_users perms[:read_users]
+ end
+ end
+
+ def share_permissions(name)
+ wmi = WIN32OLE.connect("winmgmts://")
+ shares = wmi.ExecQuery("SELECT * FROM Win32_LogicalShareSecuritySetting WHERE name = '#{name}'")
+
+ # The security descriptor is an output parameter
+ sd = nil
+ begin
+ shares.ItemIndex(0).GetSecurityDescriptor(sd)
+ sd = WIN32OLE::ARGV[0]
+ rescue WIN32OLERuntimeError
+ Chef::Log.warn("Failed to retrieve any security information about the share.")
+ end
+
+ read = []
+ change = []
+ full = []
+
+ unless sd.nil?
+ sd.DACL.each do |dacl|
+ trustee = "#{dacl.Trustee.Domain}\\#{dacl.Trustee.Name}".downcase
+ case dacl.AccessMask
+ when ACCESS_FULL
+ full.push(trustee)
+ when ACCESS_CHANGE
+ change.push(trustee)
+ when ACCESS_READ
+ read.push(trustee)
+ else
+ Chef::Log.warn "Unknown access mask #{dacl.AccessMask} for user #{trustee}. This will be lost if permissions are updated"
+ end
+ end
+ end
+
+ {
+ full_users: full,
+ change_users: change,
+ read_users: read,
+ }
+ end
+
+ action_class do
+ def description_exists?(resource)
+ !resource.description.nil?
+ end
+
+ def different_description?
+ if description_exists?(new_resource) && description_exists?(current_resource)
+ new_resource.description.casecmp(current_resource.description) != 0
+ else
+ description_exists?(new_resource) || description_exists?(current_resource)
+ end
+ end
+
+ def different_path?
+ return true if current_resource.path.nil?
+ win_friendly_path(new_resource.path).casecmp(win_friendly_path(current_resource.path)) != 0
+ end
+
+ def different_members?(permission_type)
+ !(current_resource.send(permission_type.to_sym) - new_resource.send(permission_type.to_sym).map(&:downcase)).empty? ||
+ !(new_resource.send(permission_type.to_sym).map(&:downcase) - current_resource.send(permission_type.to_sym)).empty?
+ end
+
+ def find_share_by_name(name)
+ wmi = WIN32OLE.connect("winmgmts://")
+ shares = wmi.ExecQuery("SELECT * FROM Win32_Share WHERE name = '#{name}'")
+ shares.Count == 0 ? nil : shares.ItemIndex(0)
+ end
+
+ def delete_share
+ find_share_by_name(new_resource.share_name).delete
+ end
+
+ def create_share
+ raise "#{new_resource.path} is missing or not a directory" unless ::File.directory? new_resource.path
+ new_share_script = <<-EOH
+ $share = [wmiclass]"\\\\#{ENV['COMPUTERNAME']}\\root\\CimV2:Win32_Share"
+ $result=$share.Create('#{new_resource.path}',
+ '#{new_resource.share_name}',
+ 0,
+ 16777216,
+ '#{new_resource.description}',
+ $null,
+ $null)
+ exit $result.returnValue
+ EOH
+ r = powershell_out new_share_script
+ message = case r.exitstatus
+ when 2
+ "2 : Access Denied"
+ when 8
+ "8 : Unknown Failure"
+ when 9
+ "9 : Invalid Name"
+ when 10
+ "10 : Invalid Level"
+ when 21
+ "21 : Invalid Parameter"
+ when 22
+ "22 : Duplicate Share"
+ when 23
+ "23 : Redirected Path"
+ when 24
+ "24 : Unknown Device or Directory"
+ when 25
+ "25 : Net Name Not Found"
+ else
+ r.exitstatus.to_s
+ end
+
+ raise "Could not create share. Win32_Share.create returned #{message}" if r.error?
+ end
+
+ # set_share_permissions - Enforce the share permissions as dictated by the resource attributes
+ def set_share_permissions
+ share_permissions_script = <<-EOH
+ Function New-SecurityDescriptor
+ {
+ param (
+ [array]$ACEs
+ )
+ #Create SeCDesc object
+ $SecDesc = ([WMIClass] "\\\\$env:ComputerName\\root\\cimv2:Win32_SecurityDescriptor").CreateInstance()
+
+ foreach ($ACE in $ACEs )
+ {
+ $SecDesc.DACL += $ACE.psobject.baseobject
+ }
+
+ #Return the security Descriptor
+ return $SecDesc
+ }
+
+ Function New-ACE
+ {
+ param (
+ [string] $Name,
+ [string] $Domain,
+ [string] $Permission = "Read"
+ )
+ #Create the Trusteee Object
+ $Trustee = ([WMIClass] "\\\\$env:computername\\root\\cimv2:Win32_Trustee").CreateInstance()
+ $account = get-wmiobject Win32_Account -filter "Name = '$Name' and Domain = '$Domain'"
+ $accountSID = [WMI] "\\\\$env:ComputerName\\root\\cimv2:Win32_SID.SID='$($account.sid)'"
+
+ $Trustee.Domain = $Domain
+ $Trustee.Name = $Name
+ $Trustee.SID = $accountSID.BinaryRepresentation
+
+ #Create ACE (Access Control List) object.
+ $ACE = ([WMIClass] "\\\\$env:ComputerName\\root\\cimv2:Win32_ACE").CreateInstance()
+ switch ($Permission)
+ {
+ "Read" { $ACE.AccessMask = 1179817 }
+ "Change" { $ACE.AccessMask = 1245631 }
+ "Full" { $ACE.AccessMask = 2032127 }
+ default { throw "$Permission is not a supported permission value. Possible values are 'Read','Change','Full'" }
+ }
+
+ $ACE.AceFlags = 3
+ $ACE.AceType = 0
+ $ACE.Trustee = $Trustee
+
+ $ACE
+ }
+
+ $dacl_array = @()
+
+ EOH
+ new_resource.full_users.each do |user|
+ share_permissions_script += user_to_ace(user, "Full")
+ end
+
+ new_resource.change_users.each do |user|
+ share_permissions_script += user_to_ace(user, "Change")
+ end
+
+ new_resource.read_users.each do |user|
+ share_permissions_script += user_to_ace(user, "Read")
+ end
+
+ share_permissions_script += <<-EOH
+
+ $dacl = New-SecurityDescriptor -Aces $dacl_array
+
+ $share = get-wmiobject win32_share -filter 'Name like "#{new_resource.share_name}"'
+ $return = $share.SetShareInfo($null, '#{new_resource.description}', $dacl)
+ exit $return.returnValue
+ EOH
+ r = powershell_out(share_permissions_script)
+ raise "Could not set share permissions. Win32_Share.SedtShareInfo returned #{r.exitstatus}" if r.error?
+ end
+
+ def user_to_ace(fully_qualified_user_name, access)
+ domain, user = fully_qualified_user_name.split('\\')
+ unless domain && user
+ raise "Invalid user entry #{fully_qualified_user_name}. The user names must be specified as 'DOMAIN\\user'"
+ end
+ "\n$dacl_array += new-ace -Name '#{user}' -domain '#{domain}' -permission '#{access}'"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_shortcut.rb b/lib/chef/resource/windows_shortcut.rb
new file mode 100644
index 0000000000..a547f6558c
--- /dev/null
+++ b/lib/chef/resource/windows_shortcut.rb
@@ -0,0 +1,61 @@
+#
+# Copyright:: 2010-2017, VMware, Inc.
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Doug MacEachern <dougm@vmware.com>
+ # Creates a Windows shortcut file
+ class WindowsShortcut < Chef::Resource
+ resource_name :windows_shortcut
+ provides :windows_shortcut, os: "windows"
+
+ property :target, String
+ property :arguments, String
+ property :description, String
+ property :cwd, String
+ property :iconlocation, String
+
+ load_current_value do |desired|
+ require "win32ole" if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+
+ link = WIN32OLE.new("WScript.Shell").CreateShortcut(desired.name)
+ name desired.name
+ target(link.TargetPath)
+ arguments(link.Arguments)
+ description(link.Description)
+ cwd(link.WorkingDirectory)
+ iconlocation(link.IconLocation)
+ end
+
+ action :create do
+ converge_if_changed do
+ converge_by "creating shortcut #{new_resource.name}" do
+ link = WIN32OLE.new("WScript.Shell").CreateShortcut(new_resource.name)
+ link.TargetPath = new_resource.target unless new_resource.target.nil?
+ link.Arguments = new_resource.arguments unless new_resource.arguments.nil?
+ link.Description = new_resource.description unless new_resource.description.nil?
+ link.WorkingDirectory = new_resource.cwd unless new_resource.cwd.nil?
+ link.IconLocation = new_resource.iconlocation unless new_resource.iconlocation.nil?
+ # ignoring: WindowStyle, Hotkey
+ link.Save
+ end
+ end
+ end
+ end
+ end
+end