diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d88952 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +pkg/ + +# Berkshelf +.vagrant +/cookbooks + +Berksfile.lock + + +# Bundler +Gemfile.lock +bin/* +.bundle/* + + +.kitchen/ +.kitchen.local.yml +.idea/ +config.yml diff --git a/Berksfile b/Berksfile new file mode 100644 index 0000000..cceafc6 --- /dev/null +++ b/Berksfile @@ -0,0 +1,6 @@ +source "http://supermarket.getchef.com" + + + +metadata + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d5c5249 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +## 0.1.0 + * managed_feature LWRP supporting install or remove of windows features and roles. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1195a14 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2015 EBSCO Information Services + +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/README.md b/README.md new file mode 100644 index 0000000..5a20b27 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Windows_Feature Cookbook + +Facilitates access to the mixlibrary-core windows feature support. + +## Supported Platforms + +Windows Server 2008r2 + +## Attributes + +None + +## Usage + +### default +Include the Windows_Feature cookbook default recipe in another to recipe to provide access to Windows_Feature LWRP that makes use of the mixlibrary-core windows feature support. + +#### Sample Resource + +windows_feature_manage_feature "#{cookbook_name}_install_Basic_Auth_Feature" do + feature_name "Web-Basic-Auth" + action :install +end + +## License and Authors + +Author:: Mark Lynch (git_id: gitmlynch) + +Copyright 2015 EBSCO Information Services. License:: Apache License, Version 2.0 diff --git a/chefignore b/chefignore new file mode 100644 index 0000000..0b7fd3d --- /dev/null +++ b/chefignore @@ -0,0 +1,94 @@ +# Put files/directories that should be ignored in this file when uploading +# or sharing to the community site. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +Guardfile +Procfile + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +cookbooks/* +tmp + +# Cookbooks # +############# +CONTRIBUTING +CHANGELOG* + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile + +# Travis # +########## +.travis.yml diff --git a/metadata.rb b/metadata.rb new file mode 100644 index 0000000..4b55e70 --- /dev/null +++ b/metadata.rb @@ -0,0 +1,8 @@ +name 'windows_feature' +maintainer 'Mark Lynch' +maintainer_email 'mlynch@ebsco.com' +license 'All rights reserved' +description 'Facilitates access to the mixlibrary-core windows feature support' +long_description IO.read(File.join(File.dirname(__FILE__), "README.md")) + +version '0.1.1' diff --git a/providers/manage_feature.rb b/providers/manage_feature.rb new file mode 100644 index 0000000..5b0e221 --- /dev/null +++ b/providers/manage_feature.rb @@ -0,0 +1,138 @@ +use_inline_resources + +def whyrun_supported? + false +end + +action :install do + + if reboot_pending_status + Chef::Log.info("********* A reboot is needed because of a previous feature/role addition or removal. Please reboot to avoid errors. *****************") + end + + begin + # If this is true then don't converge + if @current_resource.exists + Chef::Log.info("#{@new_resource.feature_name} was found on node #{node[:hostname]}. Did NOT run converge action to install feature/role") + # Resource does not exist so converge + else + # Use Windows Features object to install feature specified in recipe. + @feature.install_feature + @new_resource.updated_by_last_action(true) + Chef::Log.info("#{@new_resource.feature_name} was NOT found on node #{node[:hostname]}. Ran converge action to install feature/role") + end + + rescue => e + handle_error(e) + end +end + +action :remove do + + begin + # If this is true then converge + if @current_resource.exists + # Use Windows Features object to remove feature specified in recipe. + @feature.remove_feature + @new_resource.updated_by_last_action(true) + + if reboot_pending_status + Chef::Log.info("********* A reboot is needed to complete removal of the feature/role. Please reboot to avoid errors. *****************") + end + Chef::Log.info("#{@new_resource.feature_name} was found on node #{node[:hostname]}. Ran converge action to remove feature/role") + + # Resource does not exist so don't converge + else + Chef::Log.info("#{@new_resource.feature_name} was NOT found on node #{node[:hostname]}. Did NOT run converge action to remove feature/role") + end + + rescue => e + handle_error(e) + end +end + +def load_current_resource + + if @new_resource.feature_name.to_s.strip.length == 0 + raise ("feature_name cannot be '#{@new_resource.feature_name}'. Must match the name of an available Windows Feature or Role, e.g. Web-Basic-Auth") + end + + require 'mixlibrary/core/windows/features' + + begin + @current_resource = Chef::Resource::WindowsFeatureManageFeature.new(@new_resource.name) + @current_resource.feature_name(@new_resource.feature_name) + + @feature = Mixlibrary::Core::Windows::Features.new(@new_resource.feature_name) + + # Initialize the current_resource to false that way we always run the + # idempotency check which determines if the resource is in the desired state + @current_resource.exists = false + + # Use Windows Features object to do idompotency check. + if @feature.is_installed? + @current_resource.exists = true + end + + rescue => e + Chef::Log.error("#{cookbook_name}_providers/manager_feature:load_current_resource - Failed to initialize instance of Mixlibrary::Core::Windows::Features.new(#{@new_resource.feature_name})") + raise e + end +end + +def reboot_pending_status + + require 'mixlibrary/core/apps/shell' + + # The bulk of the following powershell script was "borrowed" from the Get-PendingReboot.ps1 script at + # https://gallery.technet.microsoft.com/scriptcenter/Get-PendingReboot-Query-bdb79542 authored by Brian Wilhite + + # In this powershell script we just check if a reboot is needed because of previous feature/role addition or removal. + script = <<-EOF + + $Computer = "#{node['hostname']}" + + # Making registry connection to the local/remote computer + $RegCon = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]"LocalMachine",$Computer) + + $RegSubKeysCBS = $RegCon.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\").GetSubKeyNames() + $CBSRebootPend = $RegSubKeysCBS -contains "RebootPending" + + $RegCon.Close() + + if($CBSRebootPend){ + exit 55 + } + else{ + exit 54 + } + EOF + + result = Mixlibrary::Core::Shell.windows_script_out(:powershell,script) + # Add Chef log debug for result.inspect + Chef::Log.debug(result.inspect) + + exit_status = result.exitstatus + + if exit_status == 55 + return true + elsif exit_status == 54 + return false + else + raise "Something unexpected happened while checking the reboot_pending status" + end + +end + +def handle_error(err) + # Strip all whitespace from error message to parse for specific string. + estr = err.to_s.gsub(/\s+/, "") + # If error message contains "restart the computer", without whitespace, then notify reboot resource. + if estr.include?("Pleaserestartthecomputer") + Chef::Log.error("@feature.install_feature experienced an error because the computer needs to be rebooted before adding/removing a feature/role.") + raise err + else + Chef::Log.error("@feature.install_feature experienced an unknown error. Raising exception") + raise err + end +end diff --git a/recipes/default.rb b/recipes/default.rb new file mode 100644 index 0000000..faf1619 --- /dev/null +++ b/recipes/default.rb @@ -0,0 +1,55 @@ +# +# Cookbook Name:: windows_feature +# Recipe:: default +# +# Copyright (C) 2015 EBSCO +# +# All rights reserved - Do Not Redistribute +# + +# Install the mixlibary-core gem and it's dependencies. +# This is required for the manage_feature LWRP to work. + +gem_version="0.0.13" + +myexecuteaction = execute 'gem update --system' do + not_if do + + def isgreater + #The Gem executable version needs to be a certain + myver=Gem::Version.new(Gem::VERSION) + versionsegments=myver.segments + Chef::Log::debug("Version found: #{myver}") + + #Greater than or equal to 2.4 + raise "Invalid version detected" if versionsegments.length < 2 + return false if versionsegments[0] < 2 + return false if versionsegments[1] < 4 + + return true + end + + ::Gem.clear_paths + retval = isgreater() + Chef::Log::debug("Is greater:#{retval}") + #return --- Cannot use the return statement - breaks chef D apparently + retval + end + action :nothing +end + +myexecuteaction.run_action(:run) + +mycustomgem = gem_package "eis_lib_chefcore_install_mixlibrary_core" do + package_name "mixlibrary-core" + options "--minimal-deps" + version gem_version + action :nothing +end + +mycustomgem.run_action(:install) + +#Make available in current process by clearing paths +#Require calls to mixlib need to be in method calls since they need to be "delayed" because of Chef loading order. https://sethvargo.com/using-gems-with-chef/ +#Right now we are telling everyone to lazy load their require statements. No matter what this is a hack. Might be a Chef issue that needs to be opened up. +::Gem.clear_paths diff --git a/resources/manage_feature.rb b/resources/manage_feature.rb new file mode 100644 index 0000000..0df13c0 --- /dev/null +++ b/resources/manage_feature.rb @@ -0,0 +1,8 @@ +actions :install, :remove + +default_action :install + +# Name the feature or role. +attribute :feature_name, :kind_of => String + +attr_accessor :exists