[DRE-commits] [chef] 01/01: Imported Upstream version 12.6.0

Antonio Terceiro terceiro at moszumanska.debian.org
Thu Jan 28 12:32:04 UTC 2016


This is an automated email from the git hooks/post-receive script.

terceiro pushed a commit to annotated tag upstream/12.6.0
in repository chef.

commit 39a282789b7ec89d53db8a43bafcdd295f9eaea5
Author: Antonio Terceiro <terceiro at debian.org>
Date:   Thu Jan 28 09:55:17 2016 -0200

    Imported Upstream version 12.6.0
---
 CONTRIBUTING.md                                    |   46 +-
 Gemfile                                            |   51 +
 README.md                                          |   18 +-
 Rakefile                                           |   37 +-
 chef-windows.gemspec                               |   21 +
 chef.gemspec                                       |   58 +
 distro/common/man/man1/knife-cookbook-site.1       |   22 +-
 distro/common/markdown/man1/chef-shell.mkd         |    8 +-
 distro/common/markdown/man1/knife-bootstrap.mkd    |    2 +-
 distro/common/markdown/man1/knife-client.mkd       |    2 +-
 distro/common/markdown/man1/knife-configure.mkd    |    2 +-
 .../common/markdown/man1/knife-cookbook-site.mkd   |    2 +-
 distro/common/markdown/man1/knife-cookbook.mkd     |    8 +-
 distro/common/markdown/man1/knife-data-bag.mkd     |    2 +-
 distro/common/markdown/man1/knife-environment.mkd  |    6 +-
 distro/common/markdown/man1/knife-exec.mkd         |    2 +-
 distro/common/markdown/man1/knife-index.mkd        |    2 +-
 distro/common/markdown/man1/knife-node.mkd         |    2 +-
 distro/common/markdown/man1/knife-role.mkd         |    6 +-
 distro/common/markdown/man1/knife-search.mkd       |    4 +-
 distro/common/markdown/man1/knife-ssh.mkd          |    2 +-
 distro/common/markdown/man1/knife-status.mkd       |    2 +-
 distro/common/markdown/man1/knife-tag.mkd          |    2 +-
 distro/common/markdown/man1/knife.mkd              |    4 +-
 distro/common/markdown/man8/chef-client.mkd        |    3 +-
 distro/common/markdown/man8/chef-expander.mkd      |    3 +-
 distro/common/markdown/man8/chef-expanderctl.mkd   |    3 +-
 distro/common/markdown/man8/chef-server-webui.mkd  |    2 +-
 distro/common/markdown/man8/chef-server.mkd        |    3 +-
 distro/common/markdown/man8/chef-solo.mkd          |    4 +-
 distro/common/markdown/man8/chef-solr.mkd          |    2 +-
 distro/powershell/chef/chef.psm1                   |  327 +++++
 lib/chef.rb                                        |    2 +-
 lib/chef/api_client.rb                             |   10 +-
 lib/chef/api_client/registration.rb                |   13 +-
 lib/chef/api_client_v1.rb                          |  325 +++++
 lib/chef/application.rb                            |   90 +-
 lib/chef/application/apply.rb                      |   31 +-
 lib/chef/application/client.rb                     |   50 +-
 lib/chef/application/knife.rb                      |    4 +-
 lib/chef/application/solo.rb                       |   10 +-
 lib/chef/application/windows_service.rb            |   27 +-
 lib/chef/application/windows_service_manager.rb    |   36 +-
 lib/chef/audit/audit_reporter.rb                   |   19 +-
 .../zen_follower.rb => lib/chef/audit/logger.rb    |   24 +-
 lib/chef/audit/runner.rb                           |    7 +-
 lib/chef/chef_class.rb                             |  117 +-
 lib/chef/chef_fs/chef_fs_data_store.rb             |  183 ++-
 lib/chef/chef_fs/config.rb                         |   46 +-
 .../chef_fs/data_handler/client_data_handler.rb    |    4 +-
 lib/chef/chef_fs/file_pattern.rb                   |   19 +-
 lib/chef/chef_fs/file_system/acl_dir.rb            |    7 +-
 lib/chef/chef_fs/file_system/acls_dir.rb           |    6 +-
 lib/chef/chef_fs/file_system/base_fs_dir.rb        |    5 -
 lib/chef/chef_fs/file_system/base_fs_object.rb     |    7 +-
 .../chef_repository_file_system_cookbook_dir.rb    |   11 +-
 .../chef_repository_file_system_cookbook_entry.rb  |   11 +-
 .../chef_repository_file_system_cookbooks_dir.rb   |   27 +-
 .../chef_repository_file_system_entry.rb           |   13 +-
 .../chef_repository_file_system_root_dir.rb        |   26 +-
 .../chef_fs/file_system/chef_server_root_dir.rb    |   14 +-
 lib/chef/chef_fs/file_system/cookbook_dir.rb       |    8 +-
 lib/chef/chef_fs/file_system/cookbook_subdir.rb    |    5 +
 lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb  |    2 +-
 lib/chef/chef_fs/file_system/cookbooks_dir.rb      |   14 +-
 lib/chef/chef_fs/file_system/data_bags_dir.rb      |    8 +-
 lib/chef/chef_fs/file_system/environments_dir.rb   |    2 +-
 lib/chef/chef_fs/file_system/file_system_entry.rb  |   27 +-
 lib/chef/chef_fs/file_system/memory_dir.rb         |    5 +-
 lib/chef/chef_fs/file_system/multiplexed_dir.rb    |   15 +
 lib/chef/chef_fs/file_system/nodes_dir.rb          |    2 +-
 .../file_system/organization_members_entry.rb      |    4 +-
 lib/chef/chef_fs/file_system/rest_list_dir.rb      |   13 +-
 lib/chef/chef_fs/knife.rb                          |   42 +-
 lib/chef/chef_fs/path_utils.rb                     |   99 +-
 lib/chef/client.rb                                 |  820 ++++++++---
 lib/chef/config.rb                                 |  743 +---------
 .../whyrun_safe_ruby_block.rb => constants.rb}     |   21 +-
 lib/chef/cookbook/cookbook_collection.rb           |   15 +-
 lib/chef/cookbook/cookbook_version_loader.rb       |    2 +-
 lib/chef/cookbook/metadata.rb                      |  162 ++-
 lib/chef/cookbook/remote_file_vendor.rb            |    2 +-
 lib/chef/cookbook/synchronizer.rb                  |    2 +-
 lib/chef/cookbook_loader.rb                        |    2 +-
 lib/chef/cookbook_site_streaming_uploader.rb       |   22 +-
 lib/chef/cookbook_version.rb                       |   14 +-
 lib/chef/data_bag.rb                               |    2 +-
 lib/chef/data_bag_item.rb                          |    2 +-
 ...run_safe_ruby_block.rb => delayed_evaluator.rb} |   15 +-
 lib/chef/deprecation/mixin/template.rb             |    3 +-
 lib/chef/deprecation/provider/cookbook_file.rb     |    2 +-
 lib/chef/deprecation/provider/file.rb              |    2 +-
 lib/chef/deprecation/provider/remote_directory.rb  |   52 +
 lib/chef/deprecation/provider/remote_file.rb       |    3 +-
 lib/chef/deprecation/provider/template.rb          |    2 +-
 lib/chef/deprecation/warnings.rb                   |    7 +-
 lib/chef/digester.rb                               |    6 +-
 lib/chef/dsl/chef_provisioning.rb                  |   57 +
 lib/chef/dsl/cheffish.rb                           |   64 +
 lib/chef/dsl/{recipe.rb => declare_resource.rb}    |  103 +-
 lib/chef/dsl/definitions.rb                        |   44 +
 lib/chef/dsl/platform_introspection.rb             |    6 +-
 lib/chef/dsl/reboot_pending.rb                     |    5 +-
 lib/chef/dsl/recipe.rb                             |  189 +--
 lib/chef/dsl/resources.rb                          |   57 +
 lib/chef/event_dispatch/base.rb                    |   85 +-
 lib/chef/event_dispatch/dispatcher.rb              |   34 +-
 lib/chef/event_dispatch/dsl.rb                     |   64 +
 lib/chef/event_dispatch/events_output_stream.rb    |    8 +
 lib/chef/event_loggers/windows_eventlog.rb         |   12 +-
 lib/chef/exceptions.rb                             |   64 +-
 lib/chef/file_access_control/unix.rb               |   29 +-
 lib/chef/file_content_management/deploy/cp.rb      |    4 +-
 lib/chef/file_content_management/deploy/mv_unix.rb |    8 +-
 .../file_content_management/deploy/mv_windows.rb   |   24 +-
 lib/chef/file_content_management/tempfile.rb       |    2 +-
 lib/chef/formatters/base.rb                        |   10 +
 lib/chef/formatters/doc.rb                         |   84 +-
 .../error_inspectors/api_error_formatting.rb       |   20 +
 .../error_inspectors/compile_error_inspector.rb    |   68 +-
 .../cookbook_resolve_error_inspector.rb            |    2 +
 .../cookbook_sync_error_inspector.rb               |    2 +
 .../error_inspectors/node_load_error_inspector.rb  |    2 +
 .../registration_error_inspector.rb                |    4 +
 .../error_inspectors/resource_failure_inspector.rb |   12 +-
 .../run_list_expansion_error_inspector.rb          |    2 +
 lib/chef/formatters/indentable_output_stream.rb    |    5 +
 lib/chef/formatters/minimal.rb                     |    4 +-
 .../guard_interpreter/default_guard_interpreter.rb |    2 +
 .../resource_guard_interpreter.rb                  |   10 +-
 lib/chef/http.rb                                   |   22 +-
 lib/chef/http/authenticator.rb                     |    8 +
 lib/chef/http/basic_client.rb                      |   16 +-
 lib/chef/http/decompressor.rb                      |    4 +-
 lib/chef/http/http_request.rb                      |    2 +-
 lib/chef/http/json_input.rb                        |    7 +-
 lib/chef/json_compat.rb                            |    1 +
 lib/chef/key.rb                                    |  271 ++++
 lib/chef/knife.rb                                  |  130 +-
 lib/chef/knife/bootstrap.rb                        |  110 +-
 lib/chef/knife/bootstrap/chef_vault_handler.rb     |    1 +
 lib/chef/knife/bootstrap/client_builder.rb         |   16 +
 lib/chef/knife/bootstrap/templates/README.md       |    7 +-
 .../knife/bootstrap/templates/archlinux-gems.erb   |   76 -
 lib/chef/knife/bootstrap/templates/chef-aix.erb    |   72 -
 lib/chef/knife/bootstrap/templates/chef-full.erb   |  195 ++-
 lib/chef/knife/client_bulk_delete.rb               |    4 +-
 lib/chef/knife/client_create.rb                    |   88 +-
 lib/chef/knife/client_delete.rb                    |    6 +-
 lib/chef/knife/client_edit.rb                      |   12 +-
 lib/chef/knife/client_key_create.rb                |   67 +
 lib/chef/knife/client_key_delete.rb                |   76 +
 lib/chef/knife/client_key_edit.rb                  |   80 ++
 lib/chef/knife/client_key_list.rb                  |   69 +
 lib/chef/knife/client_key_show.rb                  |   76 +
 lib/chef/knife/client_list.rb                      |    4 +-
 lib/chef/knife/client_reregister.rb                |    4 +-
 lib/chef/knife/client_show.rb                      |    4 +-
 lib/chef/knife/cookbook_create.rb                  |    2 +-
 lib/chef/knife/cookbook_site_download.rb           |    2 +-
 lib/chef/knife/cookbook_site_install.rb            |    8 +-
 lib/chef/knife/cookbook_site_share.rb              |   12 +-
 lib/chef/knife/cookbook_site_unshare.rb            |    4 +-
 lib/chef/knife/core/bootstrap_context.rb           |   17 +-
 lib/chef/knife/core/custom_manifest_loader.rb      |   69 +
 lib/chef/knife/core/gem_glob_loader.rb             |  138 ++
 lib/chef/knife/core/generic_presenter.rb           |    2 +-
 lib/chef/knife/core/hashed_command_loader.rb       |   80 ++
 lib/chef/knife/core/node_presenter.rb              |   27 +-
 lib/chef/knife/core/object_loader.rb               |    1 +
 lib/chef/knife/core/subcommand_loader.rb           |  277 ++--
 lib/chef/knife/key_create.rb                       |  108 ++
 lib/chef/knife/key_create_base.rb                  |   50 +
 lib/chef/knife/key_delete.rb                       |   55 +
 lib/chef/knife/key_edit.rb                         |  114 ++
 lib/chef/knife/key_edit_base.rb                    |   55 +
 lib/chef/knife/key_list.rb                         |   88 ++
 lib/chef/knife/key_list_base.rb                    |   45 +
 lib/chef/knife/key_show.rb                         |   53 +
 lib/chef/knife/node_run_list_remove.rb             |   13 +-
 lib/chef/knife/null.rb                             |   10 +
 .../knife/{user_create.rb => osc_user_create.rb}   |    8 +-
 .../knife/{user_delete.rb => osc_user_delete.rb}   |    9 +-
 lib/chef/knife/{user_edit.rb => osc_user_edit.rb}  |    9 +-
 lib/chef/knife/{user_list.rb => osc_user_list.rb}  |    9 +-
 .../{user_reregister.rb => osc_user_reregister.rb} |    9 +-
 lib/chef/knife/{user_show.rb => osc_user_show.rb}  |    9 +-
 lib/chef/knife/rehash.rb                           |   62 +
 lib/chef/knife/search.rb                           |    6 +-
 lib/chef/knife/ssh.rb                              |  178 ++-
 lib/chef/knife/ssl_check.rb                        |   11 +-
 lib/chef/knife/ssl_fetch.rb                        |    5 +-
 lib/chef/knife/status.rb                           |   15 +-
 lib/chef/knife/user_create.rb                      |  133 +-
 lib/chef/knife/user_delete.rb                      |   56 +-
 lib/chef/knife/user_edit.rb                        |   47 +-
 lib/chef/knife/user_key_create.rb                  |   69 +
 lib/chef/knife/user_key_delete.rb                  |   76 +
 lib/chef/knife/user_key_edit.rb                    |   80 ++
 lib/chef/knife/user_key_list.rb                    |   69 +
 lib/chef/knife/user_key_show.rb                    |   76 +
 lib/chef/knife/user_list.rb                        |    7 +-
 lib/chef/knife/user_reregister.rb                  |   49 +-
 lib/chef/knife/user_show.rb                        |   35 +-
 lib/chef/local_mode.rb                             |    5 +
 lib/chef/log.rb                                    |   22 +-
 lib/chef/log/syslog.rb                             |   46 +
 lib/chef/log/winevt.rb                             |   99 ++
 lib/chef/mixin/api_version_request_handling.rb     |   66 +
 lib/chef/mixin/convert_to_class_name.rb            |   14 +-
 lib/chef/mixin/deprecation.rb                      |   24 +
 lib/chef/mixin/get_source_from_package.rb          |    9 +-
 lib/chef/mixin/params_validate.rb                  |  497 +++++--
 lib/chef/mixin/powershell_out.rb                   |   98 ++
 lib/chef/mixin/properties.rb                       |  302 ++++
 lib/chef/mixin/provides.rb                         |   28 +-
 lib/chef/mixin/{which.rb => proxified_socket.rb}   |   33 +-
 .../cat.rb => lib/chef/mixin/subclass_directive.rb |   26 +-
 lib/chef/mixin/template.rb                         |   48 +
 .../pacman_package.rb => mixin/unformatter.rb}     |   20 +-
 .../powershell/ps_credential.rb => mixin/uris.rb}  |   34 +-
 lib/chef/mixin/which.rb                            |    2 +-
 lib/chef/mixin/wide_string.rb                      |   72 +
 lib/chef/mixin/windows_architecture_helper.rb      |   61 +-
 lib/chef/mixin/windows_env_helper.rb               |   14 +-
 lib/chef/monkey_patches/webrick-utils.rb           |   51 +
 lib/chef/monkey_patches/win32/registry.rb          |   72 +
 lib/chef/node.rb                                   |  158 ++-
 lib/chef/node_map.rb                               |  242 ++--
 .../warnings.rb => platform/handler_map.rb}        |   33 +-
 .../chef/platform/priority_map.rb                  |   32 +-
 .../provider_handler_map.rb}                       |   19 +-
 lib/chef/platform/provider_mapping.rb              |  289 +---
 lib/chef/platform/provider_priority_map.rb         |   89 +-
 lib/chef/platform/query_helpers.rb                 |   78 +-
 lib/chef/platform/rebooter.rb                      |    2 +-
 .../resource_handler_map.rb}                       |   19 +-
 lib/chef/platform/resource_priority_map.rb         |   36 +-
 lib/chef/platform/service_helpers.rb               |   88 +-
 lib/chef/policy_builder.rb                         |    9 +-
 lib/chef/policy_builder/dynamic.rb                 |  186 +++
 lib/chef/policy_builder/expand_node_object.rb      |   48 +-
 lib/chef/policy_builder/policyfile.rb              |  175 ++-
 lib/chef/property.rb                               |  607 ++++++++
 lib/chef/provider.rb                               |  290 +++-
 lib/chef/provider/batch.rb                         |    8 +
 lib/chef/provider/cron/unix.rb                     |    1 +
 lib/chef/provider/deploy.rb                        |   14 +-
 lib/chef/provider/directory.rb                     |   19 +-
 lib/chef/provider/dsc_resource.rb                  |   79 +-
 lib/chef/provider/dsc_script.rb                    |    2 +-
 lib/chef/provider/execute.rb                       |   29 +-
 lib/chef/provider/file.rb                          |    9 +-
 lib/chef/provider/group.rb                         |    2 +-
 lib/chef/provider/group/aix.rb                     |    1 +
 lib/chef/provider/group/dscl.rb                    |    2 +-
 lib/chef/provider/group/gpasswd.rb                 |    1 +
 lib/chef/provider/group/groupmod.rb                |    2 +-
 lib/chef/provider/group/pw.rb                      |    3 +-
 lib/chef/provider/group/suse.rb                    |    2 +
 lib/chef/provider/group/usermod.rb                 |    3 +-
 lib/chef/provider/group/windows.rb                 |    2 +-
 lib/chef/provider/ifconfig.rb                      |    6 +-
 lib/chef/provider/ifconfig/aix.rb                  |    1 +
 lib/chef/provider/ifconfig/debian.rb               |    2 +
 lib/chef/provider/ifconfig/redhat.rb               |    1 +
 lib/chef/provider/lwrp_base.rb                     |  151 +-
 lib/chef/provider/mount.rb                         |   11 +-
 lib/chef/provider/mount/aix.rb                     |    3 +-
 lib/chef/provider/mount/mount.rb                   |    2 +
 lib/chef/provider/mount/solaris.rb                 |    2 +
 lib/chef/provider/ohai.rb                          |    1 +
 lib/chef/provider/package.rb                       |  137 +-
 lib/chef/provider/package/aix.rb                   |   16 +-
 lib/chef/provider/package/apt.rb                   |    7 +-
 lib/chef/provider/package/dpkg.rb                  |  227 ++-
 lib/chef/provider/package/easy_install.rb          |   10 +-
 lib/chef/provider/package/freebsd/base.rb          |    4 +-
 lib/chef/provider/package/freebsd/pkg.rb           |   12 +-
 lib/chef/provider/package/freebsd/pkgng.rb         |   10 +-
 lib/chef/provider/package/freebsd/port.rb          |    8 +-
 lib/chef/provider/package/homebrew.rb              |    5 +-
 lib/chef/provider/package/ips.rb                   |    9 +-
 lib/chef/provider/package/macports.rb              |   12 +-
 lib/chef/provider/package/openbsd.rb               |   18 +-
 lib/chef/provider/package/pacman.rb                |    9 +-
 lib/chef/provider/package/paludis.rb               |    1 +
 lib/chef/provider/package/portage.rb               |    4 +
 lib/chef/provider/package/rpm.rb                   |   19 +-
 lib/chef/provider/package/rubygems.rb              |   19 +-
 lib/chef/provider/package/smartos.rb               |   11 +-
 lib/chef/provider/package/solaris.rb               |   18 +-
 lib/chef/provider/package/windows.rb               |  199 ++-
 lib/chef/provider/package/windows/exe.rb           |  129 ++
 lib/chef/provider/package/windows/msi.rb           |   56 +-
 .../package/windows/registry_uninstall_entry.rb    |   89 ++
 lib/chef/provider/package/yum.rb                   |  175 ++-
 lib/chef/provider/package/zypper.rb                |   31 +-
 lib/chef/provider/powershell_script.rb             |  215 ++-
 lib/chef/provider/reboot.rb                        |    1 +
 lib/chef/provider/registry_key.rb                  |   12 +-
 lib/chef/provider/remote_directory.rb              |  292 ++--
 lib/chef/provider/remote_file.rb                   |    1 +
 .../provider/remote_file/cache_control_data.rb     |   41 +-
 lib/chef/provider/remote_file/content.rb           |    9 +-
 lib/chef/provider/remote_file/fetcher.rb           |   30 +-
 lib/chef/provider/remote_file/http.rb              |    2 +-
 lib/chef/provider/remote_file/local_file.rb        |   14 +-
 .../remote_file/{local_file.rb => network_file.rb} |   20 +-
 lib/chef/provider/script.rb                        |    1 +
 lib/chef/provider/service.rb                       |   63 +-
 lib/chef/provider/service/aix.rb                   |   25 +-
 lib/chef/provider/service/debian.rb                |   10 +-
 lib/chef/provider/service/freebsd.rb               |    4 +-
 lib/chef/provider/service/gentoo.rb                |    6 +-
 lib/chef/provider/service/init.rb                  |    7 +-
 lib/chef/provider/service/insserv.rb               |    6 +-
 lib/chef/provider/service/invokercd.rb             |    6 +-
 lib/chef/provider/service/macosx.rb                |    8 +-
 lib/chef/provider/service/openbsd.rb               |    5 +-
 lib/chef/provider/service/redhat.rb                |   70 +-
 lib/chef/provider/service/simple.rb                |    4 +-
 lib/chef/provider/service/solaris.rb               |   60 +-
 lib/chef/provider/service/systemd.rb               |    8 +-
 lib/chef/provider/service/upstart.rb               |   15 +-
 lib/chef/provider/service/windows.rb               |    1 -
 lib/chef/provider/subversion.rb                    |   20 +-
 lib/chef/provider/template/content.rb              |   22 +-
 lib/chef/provider/user.rb                          |    4 +-
 lib/chef/provider/user/aix.rb                      |    5 +-
 lib/chef/provider/user/dscl.rb                     |  211 +--
 lib/chef/provider/user/pw.rb                       |    1 +
 lib/chef/provider/user/solaris.rb                  |   36 +-
 lib/chef/provider/user/useradd.rb                  |    1 +
 lib/chef/provider/user/windows.rb                  |    8 +-
 lib/chef/provider/windows_script.rb                |    8 +-
 lib/chef/provider_resolver.rb                      |  154 +-
 lib/chef/providers.rb                              |    1 +
 lib/chef/recipe.rb                                 |   17 +-
 lib/chef/resource.rb                               |  723 ++++++++--
 lib/chef/resource/action_class.rb                  |   87 ++
 lib/chef/resource/apt_package.rb                   |    2 -
 lib/chef/resource/bash.rb                          |    1 -
 lib/chef/resource/batch.rb                         |    2 +-
 lib/chef/resource/bff_package.rb                   |    8 -
 lib/chef/resource/breakpoint.rb                    |    8 +-
 lib/chef/resource/chef_gem.rb                      |    9 +-
 lib/chef/resource/cookbook_file.rb                 |    4 +-
 lib/chef/resource/cron.rb                          |    6 +-
 lib/chef/resource/csh.rb                           |    1 -
 lib/chef/resource/deploy.rb                        |   24 +-
 lib/chef/resource/deploy_revision.rb               |   14 -
 lib/chef/resource/directory.rb                     |    6 +-
 lib/chef/resource/dpkg_package.rb                  |   10 +-
 lib/chef/resource/dsc_resource.rb                  |   47 +-
 lib/chef/resource/dsc_script.rb                    |    9 +-
 lib/chef/resource/easy_install_package.rb          |    7 -
 lib/chef/resource/env.rb                           |    6 +-
 lib/chef/resource/erl_call.rb                      |    6 +-
 lib/chef/resource/execute.rb                       |   15 +-
 lib/chef/resource/file.rb                          |   24 +-
 lib/chef/resource/file/verification.rb             |    8 +-
 lib/chef/resource/freebsd_package.rb               |    5 -
 lib/chef/resource/gem_package.rb                   |    3 -
 lib/chef/resource/git.rb                           |    3 -
 lib/chef/resource/group.rb                         |    6 +-
 lib/chef/resource/homebrew_package.rb              |    2 -
 lib/chef/resource/http_request.rb                  |    6 +-
 lib/chef/resource/ifconfig.rb                      |    8 +-
 lib/chef/resource/ips_package.rb                   |    5 +-
 .../resource/{whyrun_safe_ruby_block.rb => ksh.rb} |   10 +-
 lib/chef/resource/link.rb                          |    8 +-
 lib/chef/resource/log.rb                           |    7 +-
 lib/chef/resource/lwrp_base.rb                     |  181 +--
 lib/chef/resource/macosx_service.rb                |    3 +-
 lib/chef/resource/macports_package.rb              |   10 +-
 lib/chef/resource/mdadm.rb                         |    7 +-
 lib/chef/resource/mount.rb                         |   14 +-
 lib/chef/resource/ohai.rb                          |    5 +-
 lib/chef/resource/openbsd_package.rb               |   17 -
 lib/chef/resource/package.rb                       |   24 +-
 lib/chef/resource/pacman_package.rb                |    7 -
 lib/chef/resource/paludis_package.rb               |    5 +-
 lib/chef/resource/perl.rb                          |    2 -
 lib/chef/resource/portage_package.rb               |    2 -
 lib/chef/resource/powershell_script.rb             |    3 +-
 lib/chef/resource/python.rb                        |    2 -
 lib/chef/resource/reboot.rb                        |    4 +-
 lib/chef/resource/registry_key.rb                  |   11 +-
 lib/chef/resource/remote_directory.rb              |    8 +-
 lib/chef/resource/remote_file.rb                   |    9 +-
 lib/chef/resource/resource_notification.rb         |   15 +-
 lib/chef/resource/route.rb                         |    9 +-
 lib/chef/resource/rpm_package.rb                   |    2 -
 lib/chef/resource/ruby.rb                          |    3 -
 lib/chef/resource/ruby_block.rb                    |    5 +-
 lib/chef/resource/scm.rb                           |    7 +-
 lib/chef/resource/script.rb                        |    4 +-
 lib/chef/resource/service.rb                       |   19 +-
 lib/chef/resource/smartos_package.rb               |    9 -
 lib/chef/resource/solaris_package.rb               |   15 +-
 lib/chef/resource/subversion.rb                    |    8 +-
 lib/chef/resource/template.rb                      |    4 -
 lib/chef/resource/timestamped_deploy.rb            |    4 -
 lib/chef/resource/user.rb                          |    7 +-
 lib/chef/resource/whyrun_safe_ruby_block.rb        |    6 -
 lib/chef/resource/windows_package.rb               |   34 +-
 lib/chef/resource/windows_script.rb                |   13 +-
 lib/chef/resource/windows_service.rb               |    6 +-
 lib/chef/resource/yum_package.rb                   |   16 +-
 ...whyrun_safe_ruby_block.rb => zypper_package.rb} |   15 +-
 lib/chef/resource_builder.rb                       |   20 +-
 lib/chef/resource_definition.rb                    |    1 +
 lib/chef/resource_reporter.rb                      |   21 +-
 lib/chef/resource_resolver.rb                      |  187 ++-
 lib/chef/resources.rb                              |    9 +-
 lib/chef/rest.rb                                   |    3 +-
 lib/chef/run_context.rb                            |  531 +++++--
 lib/chef/run_list/run_list_expansion.rb            |   47 +
 lib/chef/run_list/versioned_recipe_list.rb         |   34 +
 lib/chef/run_lock.rb                               |   51 +-
 lib/chef/run_status.rb                             |    6 +-
 lib/chef/runner.rb                                 |   25 +
 lib/chef/search/query.rb                           |   18 +-
 lib/chef/server_api.rb                             |    2 +
 lib/chef/shell.rb                                  |    2 +-
 lib/chef/user.rb                                   |   29 +-
 lib/chef/user_v1.rb                                |  335 +++++
 lib/chef/util/backup.rb                            |   10 +-
 lib/chef/util/diff.rb                              |    4 +-
 lib/chef/util/path_helper.rb                       |  208 +--
 lib/chef/util/powershell/ps_credential.rb          |    3 +
 lib/chef/util/windows.rb                           |   32 -
 lib/chef/util/windows/net_group.rb                 |  191 ++-
 lib/chef/util/windows/net_use.rb                   |  106 +-
 lib/chef/util/windows/net_user.rb                  |  192 +--
 lib/chef/util/windows/volume.rb                    |   38 +-
 lib/chef/version.rb                                |   16 +-
 lib/chef/win32/api.rb                              |    4 +-
 lib/chef/win32/api/file.rb                         |   72 +-
 lib/chef/win32/api/installer.rb                    |    2 +-
 lib/chef/win32/api/net.rb                          |  241 +++-
 lib/chef/win32/api/registry.rb                     |   51 +
 lib/chef/win32/api/security.rb                     |   24 +
 lib/chef/win32/api/system.rb                       |   23 +
 lib/chef/win32/api/unicode.rb                      |   43 -
 lib/chef/win32/crypto.rb                           |    3 +-
 .../ps_credential.rb => win32/eventlog.rb}         |   27 +-
 lib/chef/win32/file.rb                             |   36 +-
 lib/chef/win32/file/version_info.rb                |   93 ++
 lib/chef/win32/mutex.rb                            |    5 +-
 lib/chef/win32/net.rb                              |  344 +++++
 lib/chef/win32/process.rb                          |   13 +
 lib/chef/win32/registry.rb                         |   65 +-
 lib/chef/win32/security.rb                         |   53 +-
 lib/chef/win32/security/sid.rb                     |   17 +
 lib/chef/win32/security/token.rb                   |    2 +-
 lib/chef/win32/system.rb                           |   62 +
 lib/chef/win32/unicode.rb                          |    9 +-
 lib/chef/win32/version.rb                          |    4 -
 lib/chef/workstation_config_loader.rb              |  161 +--
 metadata.yml                                       |  319 ++++-
 .../chef-integration-test2-1.0/debian/changelog    |    5 +
 .../debian/chef-integration-test2.debhelper.log    |   45 +
 .../debian/chef-integration-test2.substvars        |    1 +
 .../debian/chef-integration-test2/DEBIAN/conffiles |    1 +
 .../debian/chef-integration-test2/DEBIAN/control   |   10 +
 .../debian/chef-integration-test2/DEBIAN/md5sums   |    1 +
 .../apt/chef-integration-test2-1.0/debian/compat   |    1 +
 .../chef-integration-test2-1.0/debian/conffiles    |    1 +
 .../apt/chef-integration-test2-1.0/debian/control  |   13 +
 .../chef-integration-test2-1.0/debian/copyright    |   34 +
 .../apt/chef-integration-test2-1.0/debian/files    |    1 +
 .../apt/chef-integration-test2-1.0/debian/rules    |   13 +
 .../debian/source/format                           |    1 +
 .../apt/chef-integration-test2_1.0-1.debian.tar.gz |  Bin 0 -> 1369 bytes
 spec/data/apt/chef-integration-test2_1.0-1.dsc     |   18 +
 .../apt/chef-integration-test2_1.0-1_amd64.build   |   91 ++
 .../apt/chef-integration-test2_1.0-1_amd64.changes |   31 +
 .../apt/chef-integration-test2_1.0-1_amd64.deb     |  Bin 0 -> 1694 bytes
 .../apt/chef-integration-test2_1.0.orig.tar.gz     |  Bin 0 -> 248 bytes
 .../openldap/templates/default/helpers.erb         |   14 +
 .../templates/default/nested_openldap_partials.erb |    1 +
 .../openldap/templates/default/nested_partial.erb  |    1 +
 spec/data/dsc_lcm.pfx                              |  Bin 0 -> 2597 bytes
 spec/data/lwrp/providers/buck_passer.rb            |   20 +-
 spec/data/lwrp/providers/buck_passer_2.rb          |   20 +-
 .../embedded_resource_accesses_providers_scope.rb  |   16 +-
 spec/data/lwrp_override/resources/foo.rb           |    5 +
 .../cookbooks/include/recipes/default.rb           |   24 +
 .../cookbooks/include/recipes/includee.rb          |    3 +
 spec/data/trusted_certs/opscode.pem                |  109 +-
 spec/functional/application_spec.rb                |    2 +-
 spec/functional/audit/runner_spec.rb               |   66 +-
 spec/functional/dsl/reboot_pending_spec.rb         |   76 +-
 spec/functional/knife/cookbook_delete_spec.rb      |   24 +-
 spec/functional/knife/ssh_spec.rb                  |   30 +-
 spec/functional/mixin/powershell_out_spec.rb       |   43 +
 spec/functional/notifications_spec.rb              |   78 +-
 .../provider/whyrun_safe_ruby_block_spec.rb        |    2 +-
 spec/functional/rebooter_spec.rb                   |    4 +-
 spec/functional/resource/aix_service_spec.rb       |    4 +-
 spec/functional/resource/aixinit_service_spec.rb   |    2 +-
 spec/functional/resource/deploy_revision_spec.rb   |    2 +-
 spec/functional/resource/dpkg_package_spec.rb      |  339 +++++
 spec/functional/resource/dsc_resource_spec.rb      |    2 +
 spec/functional/resource/dsc_script_spec.rb        |   93 +-
 spec/functional/resource/execute_spec.rb           |   11 +-
 spec/functional/resource/file_spec.rb              |   25 +
 spec/functional/resource/group_spec.rb             |  116 +-
 spec/functional/resource/ifconfig_spec.rb          |    4 +-
 spec/functional/resource/link_spec.rb              |   16 +-
 spec/functional/resource/mount_spec.rb             |    7 +-
 spec/functional/resource/package_spec.rb           |    4 +-
 ...owershell_spec.rb => powershell_script_spec.rb} |  164 ++-
 spec/functional/resource/user/useradd_spec.rb      |   28 +-
 spec/functional/resource/user/windows_spec.rb      |  133 ++
 spec/functional/resource/windows_package_spec.rb   |  177 +++
 spec/functional/resource/windows_service_spec.rb   |    2 +-
 spec/functional/run_lock_spec.rb                   |  557 +++++---
 spec/functional/shell_spec.rb                      |   35 +-
 .../{registry_helper_spec.rb => registry_spec.rb}  |   39 +-
 spec/functional/win32/service_manager_spec.rb      |    4 +-
 spec/functional/win32/sid_spec.rb                  |   55 +
 spec/functional/win32/version_info_spec.rb         |   50 +
 spec/integration/client/client_spec.rb             |  213 ++-
 spec/integration/knife/chef_repo_path_spec.rb      |   24 +-
 spec/integration/knife/deps_spec.rb                |   22 +-
 spec/integration/knife/download_spec.rb            |   13 +
 spec/integration/knife/list_spec.rb                |    8 +
 spec/integration/knife/upload_spec.rb              |   49 +-
 .../recipes/lwrp_inline_resources_spec.rb          |  118 +-
 ...{lwrp_inline_resources_spec.rb => lwrp_spec.rb} |   45 +-
 spec/integration/recipes/provider_choice.rb        |   36 +
 spec/integration/recipes/recipe_dsl_spec.rb        | 1492 ++++++++++++++++++++
 spec/integration/recipes/remote_directory.rb       |   74 +
 spec/integration/recipes/resource_action_spec.rb   |  458 ++++++
 .../recipes/resource_converge_if_changed_spec.rb   |  495 +++++++
 spec/integration/recipes/resource_load_spec.rb     |  206 +++
 spec/integration/solo/solo_spec.rb                 |   34 +
 spec/spec_helper.rb                                |   62 +-
 spec/support/key_helpers.rb                        |  104 ++
 .../support/lib/chef/provider/openldap_includer.rb |   15 +-
 spec/support/lib/chef/resource/cat.rb              |    1 -
 .../lib/chef/resource/one_two_three_four.rb        |    6 +-
 .../{with_state.rb => openldap_includer.rb}        |   16 +-
 spec/support/lib/chef/resource/with_state.rb       |    9 -
 spec/support/lib/chef/resource/zen_follower.rb     |    5 -
 spec/support/lib/chef/resource/zen_master.rb       |    7 +-
 spec/support/mock/platform.rb                      |    2 +-
 spec/support/pedant/Gemfile                        |    3 -
 spec/support/pedant/pedant_config.rb               |  129 --
 spec/support/pedant/run_pedant.rb                  |   63 -
 spec/support/pedant/stickywicket.pem               |   27 -
 spec/support/platform_helpers.rb                   |   21 +
 spec/support/shared/context/client.rb              |  277 ++++
 .../shared/context/win32.rb}                       |   28 +-
 spec/support/shared/examples/client.rb             |   53 +
 spec/support/shared/functional/file_resource.rb    |    4 -
 .../shared/functional/securable_resource.rb        |   70 +-
 .../securable_resource_with_reporting.rb           |    8 +-
 spec/support/shared/functional/win32_service.rb    |    3 +-
 spec/support/shared/functional/windows_script.rb   |   89 +-
 .../shared/integration/integration_helper.rb       |   17 +-
 spec/support/shared/shared_examples.rb             |    2 +-
 spec/support/shared/unit/api_versioning.rb         |   77 +
 spec/support/shared/unit/execute_resource.rb       |    5 +
 .../support/shared/unit/knife_shared.rb            |   28 +-
 spec/support/shared/unit/mock_shellout.rb          |   46 +
 spec/support/shared/unit/platform_introspector.rb  |    7 +
 spec/support/shared/unit/provider/file.rb          |   53 +-
 spec/support/shared/unit/user_and_client_shared.rb |  115 ++
 spec/tiny_server.rb                                |    8 +-
 spec/unit/api_client/registration_spec.rb          |    9 +-
 spec/unit/api_client_spec.rb                       |   20 +-
 .../{api_client_spec.rb => api_client_v1_spec.rb}  |  251 +++-
 spec/unit/application/client_spec.rb               |   28 +-
 spec/unit/application/knife_spec.rb                |    4 +-
 spec/unit/application/solo_spec.rb                 |    7 +-
 spec/unit/application_spec.rb                      |  182 +--
 spec/unit/audit/audit_reporter_spec.rb             |   72 +-
 spec/unit/audit/logger_spec.rb                     |   42 +
 spec/unit/audit/runner_spec.rb                     |    4 +-
 spec/unit/chef_class_spec.rb                       |   27 +-
 spec/unit/chef_fs/file_pattern_spec.rb             |   18 +-
 .../file_system/cookbook_subdir_spec.rb}           |   24 +-
 spec/unit/chef_fs/path_util_spec.rb                |  108 ++
 spec/unit/client_spec.rb                           |  474 ++-----
 spec/unit/config_spec.rb                           |  545 +------
 spec/unit/cookbook/cookbook_version_loader_spec.rb |    2 +-
 spec/unit/cookbook/metadata_spec.rb                |  163 ++-
 spec/unit/cookbook/syntax_check_spec.rb            |    5 +-
 spec/unit/cookbook_loader_spec.rb                  |    2 +-
 spec/unit/cookbook_site_streaming_uploader_spec.rb |   21 -
 spec/unit/cookbook_spec.rb                         |    9 -
 spec/unit/cookbook_version_spec.rb                 |   22 +-
 spec/unit/data_bag_item_spec.rb                    |    2 +-
 spec/unit/data_bag_spec.rb                         |    4 +-
 spec/unit/deprecation_spec.rb                      |   64 +-
 spec/unit/dsl/reboot_pending_spec.rb               |   18 +-
 spec/unit/dsl/resources_spec.rb                    |   85 ++
 spec/unit/environment_spec.rb                      |    2 +-
 spec/unit/event_dispatch/dispatcher_spec.rb        |  123 ++
 spec/unit/event_dispatch/dsl_spec.rb               |   83 ++
 spec/unit/exceptions_spec.rb                       |    6 +-
 .../deploy/mv_windows_spec.rb                      |   60 +
 spec/unit/formatters/doc_spec.rb                   |   78 +
 .../error_inspectors/api_error_formatting_spec.rb  |   77 +
 .../compile_error_inspector_spec.rb                |  285 ++--
 .../resource_failure_inspector_spec.rb             |    7 +
 .../resource_guard_interpreter_spec.rb             |   10 +-
 spec/unit/http/authenticator_spec.rb               |   78 +
 spec/unit/http/basic_client_spec.rb                |   16 +
 spec/unit/http_spec.rb                             |  102 ++
 spec/unit/json_compat_spec.rb                      |    9 +-
 spec/unit/key_spec.rb                              |  634 +++++++++
 spec/unit/knife/bootstrap/client_builder_spec.rb   |   27 +
 spec/unit/knife/bootstrap_spec.rb                  |  141 +-
 spec/unit/knife/client_bulk_delete_spec.rb         |    8 +-
 spec/unit/knife/client_create_spec.rb              |  173 ++-
 spec/unit/knife/client_delete_spec.rb              |    6 +-
 spec/unit/knife/client_edit_spec.rb                |   15 +-
 spec/unit/knife/client_list_spec.rb                |    2 +-
 spec/unit/knife/client_reregister_spec.rb          |    4 +-
 spec/unit/knife/client_show_spec.rb                |    4 +-
 spec/unit/knife/cookbook_site_share_spec.rb        |    6 +-
 spec/unit/knife/core/bootstrap_context_spec.rb     |   38 +-
 .../unit/knife/core/custom_manifest_loader_spec.rb |   41 +
 ...mand_loader_spec.rb => gem_glob_loader_spec.rb} |  114 +-
 spec/unit/knife/core/hashed_command_loader_spec.rb |   93 ++
 spec/unit/knife/core/subcommand_loader_spec.rb     |  200 +--
 spec/unit/knife/core/ui_spec.rb                    |   16 +-
 spec/unit/knife/data_bag_from_file_spec.rb         |    2 +-
 spec/unit/knife/environment_from_file_spec.rb      |    2 +-
 spec/unit/knife/key_create_spec.rb                 |  224 +++
 spec/unit/knife/key_delete_spec.rb                 |  135 ++
 spec/unit/knife/key_edit_spec.rb                   |  267 ++++
 spec/unit/knife/key_helper.rb                      |   74 +
 spec/unit/knife/key_list_spec.rb                   |  216 +++
 spec/unit/knife/key_show_spec.rb                   |  126 ++
 spec/unit/knife/node_run_list_remove_spec.rb       |   17 +
 ...user_create_spec.rb => osc_user_create_spec.rb} |   11 +-
 ...user_delete_spec.rb => osc_user_delete_spec.rb} |   11 +-
 .../{user_edit_spec.rb => osc_user_edit_spec.rb}   |   11 +-
 .../{user_list_spec.rb => osc_user_list_spec.rb}   |   11 +-
 ...egister_spec.rb => osc_user_reregister_spec.rb} |   11 +-
 .../{user_show_spec.rb => osc_user_show_spec.rb}   |   11 +-
 spec/unit/knife/ssh_spec.rb                        |   49 +-
 spec/unit/knife/ssl_check_spec.rb                  |   14 +-
 spec/unit/knife/ssl_fetch_spec.rb                  |    4 +-
 spec/unit/knife/status_spec.rb                     |   26 +-
 spec/unit/knife/user_create_spec.rb                |  228 ++-
 spec/unit/knife/user_delete_spec.rb                |   42 +-
 spec/unit/knife/user_edit_spec.rb                  |   45 +-
 spec/unit/knife/user_list_spec.rb                  |   12 +-
 spec/unit/knife/user_reregister_spec.rb            |   55 +-
 spec/unit/knife/user_show_spec.rb                  |   46 +-
 spec/unit/knife_spec.rb                            |   66 +-
 spec/unit/log/syslog_spec.rb                       |   53 +
 spec/unit/log/winevt_spec.rb                       |   55 +
 spec/unit/lwrp_spec.rb                             |  467 ++++--
 .../mixin/api_version_request_handling_spec.rb     |  127 ++
 spec/unit/mixin/command_spec.rb                    |    3 +-
 .../enforce_ownership_and_permissions_spec.rb      |   20 +-
 spec/unit/mixin/params_validate_spec.rb            |    6 +-
 spec/unit/mixin/path_sanity_spec.rb                |    4 +-
 spec/unit/mixin/powershell_out_spec.rb             |   70 +
 spec/unit/mixin/properties_spec.rb                 |   97 ++
 spec/unit/mixin/proxified_socket_spec.rb           |   94 ++
 spec/unit/mixin/subclass_directive_spec.rb         |   45 +
 spec/unit/mixin/template_spec.rb                   |   10 +-
 spec/unit/mixin/unformatter_spec.rb                |   61 +
 spec/unit/mixin/uris_spec.rb                       |   57 +
 .../unit/mixin/windows_architecture_helper_spec.rb |   21 +-
 spec/unit/node_map_spec.rb                         |   17 +-
 spec/unit/node_spec.rb                             |  232 ++-
 spec/unit/platform/query_helpers_spec.rb           |  151 +-
 spec/unit/platform_spec.rb                         |   62 +-
 spec/unit/policy_builder/dynamic_spec.rb           |  275 ++++
 .../unit/policy_builder/expand_node_object_spec.rb |   75 +-
 spec/unit/policy_builder/policyfile_spec.rb        |  318 ++++-
 spec/unit/property/state_spec.rb                   |  506 +++++++
 spec/unit/property/validation_spec.rb              |  665 +++++++++
 spec/unit/property_spec.rb                         | 1150 +++++++++++++++
 spec/unit/provider/deploy/revision_spec.rb         |    2 +-
 spec/unit/provider/deploy_spec.rb                  |   16 +-
 spec/unit/provider/directory_spec.rb               |  365 +++--
 spec/unit/provider/dsc_resource_spec.rb            |   98 +-
 spec/unit/provider/dsc_script_spec.rb              |    4 +-
 spec/unit/provider/execute_spec.rb                 |  125 +-
 spec/unit/provider/ifconfig/debian_spec.rb         |   10 -
 spec/unit/provider/ifconfig_spec.rb                |   24 +-
 spec/unit/provider/mount/aix_spec.rb               |    3 +-
 spec/unit/provider/mount/mount_spec.rb             |    6 +
 spec/unit/provider/mount/windows_spec.rb           |   14 +
 spec/unit/provider/mount_spec.rb                   |   13 +-
 spec/unit/provider/package/aix_spec.rb             |   44 +-
 spec/unit/provider/package/dpkg_spec.rb            |  278 ++--
 spec/unit/provider/package/freebsd/pkg_spec.rb     |   26 +-
 spec/unit/provider/package/freebsd/pkgng_spec.rb   |   18 +-
 spec/unit/provider/package/freebsd/port_spec.rb    |   14 +-
 spec/unit/provider/package/ips_spec.rb             |   44 +-
 spec/unit/provider/package/macports_spec.rb        |   20 +-
 spec/unit/provider/package/openbsd_spec.rb         |   30 +-
 spec/unit/provider/package/pacman_spec.rb          |   10 +-
 spec/unit/provider/package/rpm_spec.rb             |  476 +++++--
 spec/unit/provider/package/rubygems_spec.rb        |   72 +-
 spec/unit/provider/package/smartos_spec.rb         |   90 +-
 spec/unit/provider/package/solaris_spec.rb         |   22 +-
 spec/unit/provider/package/windows/exe_spec.rb     |  251 ++++
 spec/unit/provider/package/windows/msi_spec.rb     |  104 +-
 spec/unit/provider/package/windows_spec.rb         |  346 ++++-
 spec/unit/provider/package/yum_spec.rb             |  237 +++-
 spec/unit/provider/package/zypper_spec.rb          |  215 +--
 spec/unit/provider/package_spec.rb                 |  867 +++++++-----
 spec/unit/provider/package_spec.rbe                |    0
 spec/unit/provider/powershell_script_spec.rb       |  106 ++
 spec/unit/provider/powershell_spec.rb              |   38 -
 spec/unit/provider/registry_key_spec.rb            |   12 +
 spec/unit/provider/remote_directory_spec.rb        |    7 +-
 .../remote_file/cache_control_data_spec.rb         |   98 +-
 spec/unit/provider/remote_file/fetcher_spec.rb     |   21 +-
 spec/unit/provider/remote_file/local_file_spec.rb  |   31 +-
 .../unit/provider/remote_file/network_file_spec.rb |   45 +
 spec/unit/provider/script_spec.rb                  |    4 +-
 spec/unit/provider/service/aix_service_spec.rb     |   37 +-
 spec/unit/provider/service/freebsd_service_spec.rb |   12 -
 spec/unit/provider/service/gentoo_service_spec.rb  |    8 +-
 spec/unit/provider/service/macosx_spec.rb          |    8 +-
 spec/unit/provider/service/openbsd_service_spec.rb |   18 +-
 spec/unit/provider/service/redhat_spec.rb          |   96 +-
 .../provider/service/solaris_smf_service_spec.rb   |  149 +-
 spec/unit/provider/service/upstart_service_spec.rb |   37 +-
 spec/unit/provider/service/windows_spec.rb         |  411 +++---
 spec/unit/provider/subversion_spec.rb              |   81 +-
 spec/unit/provider/template/content_spec.rb        |   95 +-
 spec/unit/provider/user/dscl_spec.rb               |   16 +-
 spec/unit/provider/user/solaris_spec.rb            |   75 +-
 spec/unit/provider/user/windows_spec.rb            |    4 +-
 spec/unit/provider/user_spec.rb                    |   15 +-
 spec/unit/provider_resolver_spec.rb                | 1219 +++++++++-------
 spec/unit/provider_spec.rb                         |   24 +-
 spec/unit/recipe_spec.rb                           |  131 +-
 spec/unit/registry_helper_spec.rb                  |  376 -----
 spec/unit/resource/batch_spec.rb                   |    1 +
 spec/unit/resource/breakpoint_spec.rb              |    2 +-
 spec/unit/resource/chef_gem_spec.rb                |    2 +-
 spec/unit/resource/cron_spec.rb                    |    2 +-
 spec/unit/resource/deploy_spec.rb                  |    8 +-
 spec/unit/resource/directory_spec.rb               |    2 +-
 spec/unit/resource/dsc_resource_spec.rb            |   19 +-
 spec/unit/resource/dsc_script_spec.rb              |    8 +-
 spec/unit/resource/env_spec.rb                     |    2 +-
 spec/unit/resource/erl_call_spec.rb                |    2 +-
 spec/unit/resource/file/verification_spec.rb       |   38 +-
 spec/unit/resource/file_spec.rb                    |    2 +-
 spec/unit/resource/group_spec.rb                   |    2 +-
 spec/unit/resource/ifconfig_spec.rb                |   16 +-
 .../client_list_spec.rb => resource/ksh_spec.rb}   |   28 +-
 spec/unit/resource/link_spec.rb                    |    2 +-
 spec/unit/resource/mdadm_spec.rb                   |    2 +-
 spec/unit/resource/mount_spec.rb                   |    2 +-
 spec/unit/resource/ohai_spec.rb                    |    2 +-
 ...owershell_spec.rb => powershell_script_spec.rb} |   31 +-
 spec/unit/resource/registry_key_spec.rb            |    6 +-
 spec/unit/resource/remote_file_spec.rb             |   15 +
 spec/unit/resource/resource_notification_spec.rb   |   89 +-
 spec/unit/resource/ruby_block_spec.rb              |    4 +-
 spec/unit/resource/service_spec.rb                 |    8 +-
 spec/unit/resource/subversion_spec.rb              |    4 +
 spec/unit/resource/template_spec.rb                |    2 +-
 spec/unit/resource/timestamped_deploy_spec.rb      |    3 +-
 spec/unit/resource/user_spec.rb                    |    2 +-
 spec/unit/resource/windows_package_spec.rb         |   18 +-
 spec/unit/resource/yum_package_spec.rb             |   11 +-
 spec/unit/resource_collection_spec.rb              |    2 +-
 spec/unit/resource_reporter_spec.rb                |    7 +
 spec/unit/resource_resolver_spec.rb                |   53 +
 spec/unit/resource_spec.rb                         |  772 ++++++----
 spec/unit/rest_spec.rb                             |   30 +-
 spec/unit/role_spec.rb                             |    4 +-
 spec/unit/run_context/child_run_context_spec.rb    |  133 ++
 spec/unit/run_context_spec.rb                      |   79 ++
 spec/unit/run_list/run_list_expansion_spec.rb      |   21 +-
 spec/unit/run_list/versioned_recipe_list_spec.rb   |  168 ++-
 spec/unit/run_list_spec.rb                         |    2 +-
 spec/unit/search/query_spec.rb                     |   20 +-
 spec/unit/shell_spec.rb                            |    8 +-
 spec/unit/user_spec.rb                             |   27 +-
 spec/unit/user_v1_spec.rb                          |  584 ++++++++
 spec/unit/util/path_helper_spec.rb                 |  255 ----
 spec/unit/util/powershell/ps_credential_spec.rb    |    9 +-
 spec/unit/win32/registry_spec.rb                   |  394 ++++++
 spec/unit/windows_service_spec.rb                  |  121 +-
 spec/unit/workstation_config_loader_spec.rb        |  283 ----
 tasks/external_tests.rb                            |   63 +
 tasks/maintainers.rb                               |  210 +++
 tasks/rspec.rb                                     |   15 +-
 798 files changed, 37439 insertions(+), 12702 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cc41653..b2f9ece 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -11,7 +11,7 @@ We utilize **Github Issues** for issue tracking and contributions. You can contr
 
 We have a 3 step process that utilizes **Github Issues**:
 
-1. Sign or be added to an existing [Contributor License Agreement (CLA)](https://supermarket.getchef.com/become-a-contributor).
+1. Sign or be added to an existing [Contributor License Agreement (CLA)](https://supermarket.chef.io/become-a-contributor).
 2. Create a Github Pull Request.
 3. Do [Code Review](#cr) with the **Chef Engineering Team** or **Chef Core Committers** on the pull request.
 
@@ -21,7 +21,7 @@ Chef is built to last. We strive to ensure high quality throughout the Chef expe
   this, we require a couple of things for all pull requests to Chef:
 
 1. **Tests:** To ensure high quality code and protect against future regressions, we require all the
-  code in Chef to have at least unit test coverage. See the [spec/unit](https://github.com/opscode/chef/tree/master/spec/unit)
+  code in Chef to have at least unit test coverage. See the [spec/unit](https://github.com/chef/chef/tree/master/spec/unit)
   directory for the existing tests and use ```bundle exec rake spec``` to run them.
 2. **Green Travis Run:** We use [Travis CI](https://travis-ci.org/) in order to run our tests
   continuously on all the pull requests. We require the Travis runs to succeed on every pull
@@ -63,14 +63,14 @@ You can watch the recordings of the old Code Review hangouts on the [opscodebtm]
 Licensing is very important to open source projects. It helps ensure the
   software continues to be available under the terms that the author desired.
 
-Chef uses [the Apache 2.0 license](https://github.com/opscode/chef/blob/master/LICENSE)
+Chef uses [the Apache 2.0 license](https://github.com/chef/chef/blob/master/LICENSE)
   to strike a balance between open contribution and allowing you to use the
   software however you would like to.
 
 The license tells you what rights you have that are provided by the copyright holder.
   It is important that the contributor fully understands what rights they are
   licensing and agrees to them. Sometimes the copyright holder isn't the contributor,
-  most often when the contributor is doing work for a company.
+  such as when the contributor is doing work for a company.
 
 To make a good faith effort to ensure these criteria are met, Chef requires an Individual CLA
   or a Corporate CLA for contributions. This agreement helps ensure you are aware of the
@@ -81,10 +81,10 @@ To make a good faith effort to ensure these criteria are met, Chef requires an I
 It only takes a few minutes to complete a CLA, and you retain the copyright to your contribution.
 
 You can complete our
-  [Individual CLA](https://supermarket.getchef.com/icla-signatures/new) online.
+  [Individual CLA](https://supermarket.chef.io/icla-signatures/new) online.
   If you're contributing on behalf of your employer and they retain the copyright for your works,
   have your employer fill out our
-  [Corporate CLA](https://supermarket.getchef.com/ccla-signatures/new) instead.
+  [Corporate CLA](https://supermarket.chef.io/ccla-signatures/new) instead.
 
 ### Chef Obvious Fix Policy
 
@@ -109,7 +109,7 @@ As a rule of thumb, changes are obvious fixes if they do not introduce any new f
 ```
 ------------------------------------------------------------------------
 commit 370adb3f82d55d912b0cf9c1d1e99b132a8ed3b5
-Author: danielsdeleo <dan at opscode.com>
+Author: danielsdeleo <dan at chef.io>
 Date:   Wed Sep 18 11:44:40 2013 -0700
 
   Fix typo in config file docs.
@@ -126,16 +126,16 @@ Chef Issue Tracking is handled using Github Issues.
 If you are familiar with Chef and know the component that is causing you a problem or if you
   have a feature request on a specific component you can file an issue in the corresponding
   Github project. All of our Open Source Software can be found in our
-  [Github organization](https://github.com/opscode/).
+  [Github organization](https://github.com/chef/).
 
 There is also a listing of the various Chef products and where to file issues that can be
   found in the Chef docs in the [community contributions section](https://docs.chef.io/community_contributions.html#issues-and-bug-reports).
 
-Otherwise you can file your issue in the [Chef project](https://github.com/opscode/chef/issues)
+Otherwise you can file your issue in the [Chef project](https://github.com/chef/chef/issues)
   and we will make sure it gets filed against the appropriate project.
 
-In order to decrease the back and forth an issues and help us get to the bottom of them quickly
-  we use below issue template. You can copy paste this code into the issue you are opening and
+In order to decrease the back and forth in issues, and to help us get to the bottom of them quickly
+  we use the below issue template. You can copy/paste this template into the issue you are opening and
   edit it accordingly.
 
 <a name="issuetemplate"></a>
@@ -148,33 +148,29 @@ In order to decrease the back and forth an issues and help us get to the bottom
 ### Scenario:
 [What you are trying to achieve and you can't?]
 
-
-
 ### Steps to Reproduce:
 [If you are filing an issue what are the things we need to do in order to repro your problem?]
 
-
 ### Expected Result:
 [What are you expecting to happen as the consequence of above reproduction steps?]
 
-
 ### Actual Result:
 [What actually happens after the reproduction steps?]
 ```
 
 ### Useful Github Queries
 
-Contributions go through a review process to improve code quality and avoid regressions. Managing a large number of contributions requires a workflow to provide queues for work such as triage, code review, and merging. A semi-formal process has evolved over the life of the project. Chef maintains this process pending community development and acceptance of an [RFC](https://github.com/opscode/chef-rfc). These queries will help track contributions through this process:
+Contributions go through a review process to improve code quality and avoid regressions. Managing a large number of contributions requires a workflow to provide queues for work such as triage, code review, and merging. A semi-formal process has evolved over the life of the project. Chef maintains this process pending community development and acceptance of an [RFC](https://github.com/chef/chef-rfc). These queries will help track contributions through this process:
 
-* [Issues that are not assigned to a team](https://github.com/opscode/chef/issues?q=is%3Aopen+-label%3AAIX+-label%3ABSD+-label%3Awindows+-label%3A%22Chef+Core%22++-label%3A%22Dev+Tools%22+-label%3AUbuntu+-label%3A%22Enterprise+Linux%22+-label%3A%22Ready+For+Merge%22+-label%3AMac+-label%3ASolaris+)
-* [Untriaged Issues](https://github.com/opscode/chef/issues?q=is%3Aopen+is%3Aissue+-label%3ABug+-label%3AEnhancement+-label%3A%22Tech+Cleanup%22+-label%3A%22Ready+For+Merge%22)
-* [PRs to be Reviewed](https://github.com/opscode/chef/labels/Pending%20Maintainer%20Review)
-* [Suitable for First Contribution](https://github.com/opscode/chef/labels/Easy)
+* [Issues that are not assigned to a team](https://github.com/chef/chef/issues?q=is%3Aopen+-label%3AAIX+-label%3ABSD+-label%3Awindows+-label%3A%22Chef+Core%22++-label%3A%22Dev+Tools%22+-label%3AUbuntu+-label%3A%22Enterprise+Linux%22+-label%3A%22Ready+For+Merge%22+-label%3AMac+-label%3ASolaris+)
+* [Untriaged Issues](https://github.com/chef/chef/issues?q=is%3Aopen+is%3Aissue+-label%3ABug+-label%3AEnhancement+-label%3A%22Tech+Cleanup%22+-label%3A%22Ready+For+Merge%22)
+* [PRs to be Reviewed](https://github.com/chef/chef/labels/Pending%20Maintainer%20Review)
+* [Suitable for First Contribution](https://github.com/chef/chef/labels/Easy)
 
 ## <a name="release"></a> Chef Release Cycles
 
 Our primary shipping vehicle is operating system specific packages that includes
-  all the requirements of Chef. We call these [Omnibus packages](https://github.com/opscode/omnibus-ruby)
+  all the requirements of Chef. We call these [Omnibus packages](https://github.com/chef/omnibus)
 
 We also release our software as gems to [Rubygems](https://rubygems.org/) but we strongly
   recommend using Chef packages since they are the only combination of native libraries &
@@ -194,7 +190,7 @@ We frequently make `alpha` and `beta` releases with version numbers that look li
 We do a `Minor` release approximately every 3 months and `Patch` releases on a when-needed
   basis for regressions, significant bugs, and security issues.
 
-Announcements of releases are available on [Chef Blog](http://www.getchef.com/blog) when they are
+Announcements of releases are available on [Chef Blog](http://www.chef.io/blog) when they are
   available.
 
 ## Chef Community
@@ -207,6 +203,6 @@ Chef is made possible by a strong community of developers and system administrat
 
 Also here are some additional pointers to some awesome Chef content:
 
-* [Chef Docs](http://docs.opscode.com/)
-* [Learn Chef](https://learnchef.opscode.com/)
-* [Chef Inc](http://www.getchef.com/)
+* [Chef Docs](https://docs.chef.io/)
+* [Learn Chef](https://learn.chef.io/)
+* [Chef Inc](https://www.chef.io/)
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..822a6a8
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,51 @@
+source "https://rubygems.org"
+gemspec :name => "chef"
+
+gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby"
+
+gem 'chef-config', path: "chef-config" if File.exists?(__FILE__ + '../chef-config')
+
+group(:docgen) do
+  gem "yard"
+end
+
+group(:maintenance) do
+  gem "tomlrb"
+
+  # To sync maintainers with github
+  gem "octokit"
+  gem "netrc"
+end
+
+group(:ruby_prof) do
+  # may need to disable this in insolation on fussy builds like AIX, RHEL4, etc
+  gem "ruby-prof"
+end
+
+group(:development, :test) do
+
+  gem "simplecov"
+  gem 'rack', "~> 1.5.1"
+
+
+  gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/)
+
+  # For external tests
+#  gem 'chef-zero', github: 'chef/chef-zero'
+#  gem 'cheffish', github: 'chef/cheffish'
+#  gem 'chef-provisioning'#, github: 'chef/chef-provisioning'
+#  gem 'chef-provisioning-aws', github: 'chef/chef-provisioning-aws'
+#  gem 'test-kitchen'
+#  gem 'chefspec'
+#  gem 'chef-sugar'
+#  gem 'poise', github: 'poise/poise', branch: 'deeecb890a6a0bc2037dfb09ce0fd0a8931519aa'
+#  gem 'halite', github: 'poise/halite'
+#  gem 'foodcritic', github: 'acrmp/foodcritic', branch: 'v5.0.0'
+#  gem 'chef-rewind'
+end
+
+instance_eval(ENV['GEMFILE_MOD']) if ENV['GEMFILE_MOD']
+
+# If you want to load debugging tools into the bundle exec sandbox,
+# add these additional dependencies into chef/Gemfile.local
+eval(IO.read(__FILE__ + '.local'), binding) if File.exists?(__FILE__ + '.local')
diff --git a/README.md b/README.md
index 3b1c719..8c2df43 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-# Chef 
-[![Code Climate](https://codeclimate.com/github/opscode/chef.png)](https://codeclimate.com/github/opscode/chef)
+# Chef
+[![Code Climate](https://codeclimate.com/github/chef/chef.png)](https://codeclimate.com/github/chef/chef)
 [![Build Status Master](https://travis-ci.org/chef/chef.svg?branch=master)](https://travis-ci.org/chef/chef)
 [![Build Status Master](https://ci.appveyor.com/api/projects/status/github/chef/chef?branch=master&svg=true&passingText=master%20-%20Ok&pendingText=master%20-%20Pending&failingText=master%20-%20Failing)](https://ci.appveyor.com/project/Chef/chef/branch/master)
 
@@ -9,7 +9,9 @@ Want to try Chef? Get started with [learnchef](https://learn.chef.io)
 * Source: [http://github.com/chef/chef/tree/master](http://github.com/chef/chef/tree/master)
 * Tickets/Issues: [https://github.com/chef/chef/issues](https://github.com/chef/chef/issues)
 * IRC: `#chef` and `#chef-hacking` on Freenode
-* Mailing list: [http://lists.opscode.com](http://lists.opscode.com)
+  - Join via browser: [#chef](https://webchat.freenode.net/?channels=chef), [#chef-hacking](https://webchat.freenode.net/?channels=chef-hacking)
+  - View logs: [#chef](https://botbot.me/freenode/chef/), [#chef-hacking](https://botbot.me/freenode/chef-hacking/)
+* Mailing list: [https://discourse.chef.io](https://discourse.chef.io)
 
 Chef is a configuration management tool designed to bring automation to your
 entire infrastructure.
@@ -46,7 +48,7 @@ Then get the source and install it:
 
     # Clone this repo
     git clone https://github.com/chef/chef.git
-    
+
     # cd into the source tree
     cd chef
 
@@ -68,11 +70,11 @@ read the
 
 The general development process is:
 
-1. Fork this repo and clone it to your workstation
-2. Create a feature branch for your change
-3. Write code and tests
+1. Fork this repo and clone it to your workstation.
+2. Create a feature branch for your change.
+3. Write code and tests.
 4. Push your feature branch to github and open a pull request against
-   master
+   master.
 
 Once your repository is set up, you can start working on the code. We do use
 TDD with RSpec, so you'll need to get a development environment running.
diff --git a/Rakefile b/Rakefile
index 70a45d9..25ad13f 100644
--- a/Rakefile
+++ b/Rakefile
@@ -17,40 +17,21 @@
 # limitations under the License.
 #
 
-require File.dirname(__FILE__) + '/lib/chef/version'
+VERSION = IO.read(File.expand_path("../VERSION", __FILE__)).strip
 
 require 'rubygems'
-require 'rubygems/package_task'
+require 'chef-config/package_task'
 require 'rdoc/task'
-require './tasks/rspec.rb'
+require_relative 'tasks/rspec'
+require_relative 'tasks/external_tests'
+require_relative 'tasks/maintainers'
 
-GEM_NAME = "chef"
-
-Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
-  gemspec = eval(IO.read(gemspec_path))
-  Gem::PackageTask.new(gemspec).define
-end
-
-task :install => :package do
-  sh %{gem install pkg/#{GEM_NAME}-#{Chef::VERSION}.gem --no-rdoc --no-ri}
-end
-
-task :uninstall do
-  sh %{gem uninstall #{GEM_NAME} -x -v #{Chef::VERSION} }
+ChefConfig::PackageTask.new(File.expand_path('..', __FILE__), 'Chef') do |package|
+  package.component_paths = ['chef-config']
+  package.generate_version_class = true
 end
 
-desc "Build it, tag it and ship it"
-task :ship => :gem do
-  sh("git tag #{Chef::VERSION}")
-  sh("git push opscode --tags")
-  Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
-    sh("gem push #{built_gem}")
-  end
-end
-
-task :pedant do
-  require File.expand_path('spec/support/pedant/run_pedant')
-end
+task :pedant, :chef_zero_spec
 
 task :build_eventlog do
   Dir.chdir 'ext/win32-eventlog/' do
diff --git a/chef-windows.gemspec b/chef-windows.gemspec
new file mode 100644
index 0000000..489c551
--- /dev/null
+++ b/chef-windows.gemspec
@@ -0,0 +1,21 @@
+gemspec = eval(IO.read(File.expand_path("../chef.gemspec", __FILE__)))
+
+gemspec.platform = Gem::Platform.new(["universal", "mingw32"])
+
+gemspec.add_dependency "ffi", "~> 1.9"
+gemspec.add_dependency "win32-api", "~> 1.5.3"
+gemspec.add_dependency "win32-dir", "~> 0.5.0"
+gemspec.add_dependency "win32-event", "~> 0.6.1"
+gemspec.add_dependency "win32-eventlog", "0.6.3"
+gemspec.add_dependency "win32-mmap", "~> 0.4.1"
+gemspec.add_dependency "win32-mutex", "~> 0.4.2"
+gemspec.add_dependency "win32-process", "~> 0.8.2"
+gemspec.add_dependency "win32-service", "~> 0.8.7"
+gemspec.add_dependency "windows-api", "~> 0.4.4"
+gemspec.add_dependency "wmi-lite", "~> 1.0"
+gemspec.extensions << "ext/win32-eventlog/Rakefile"
+gemspec.files += %w(ext/win32-eventlog/Rakefile ext/win32-eventlog/chef-log.man)
+
+gemspec.executables += %w( chef-service-manager chef-windows-service )
+
+gemspec
diff --git a/chef.gemspec b/chef.gemspec
new file mode 100644
index 0000000..2e33ec6
--- /dev/null
+++ b/chef.gemspec
@@ -0,0 +1,58 @@
+$:.unshift(File.dirname(__FILE__) + '/lib')
+require 'chef/version'
+
+Gem::Specification.new do |s|
+  s.name = 'chef'
+  s.version = Chef::VERSION
+  s.platform = Gem::Platform::RUBY
+  s.extra_rdoc_files = ["README.md", "CONTRIBUTING.md", "LICENSE" ]
+  s.summary = "A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure."
+  s.description = s.summary
+  s.license = "Apache-2.0"
+  s.author = "Adam Jacob"
+  s.email = "adam at chef.io"
+  s.homepage = "http://www.chef.io"
+
+  s.required_ruby_version = ">= 2.0.0"
+
+  s.add_dependency "chef-config", "= #{Chef::VERSION}"
+
+  s.add_dependency "mixlib-cli", "~> 1.4"
+  s.add_dependency "mixlib-log", "~> 1.3"
+  s.add_dependency "mixlib-authentication", "~> 1.3"
+  s.add_dependency "mixlib-shellout", "~> 2.0"
+  s.add_dependency "ohai", ">= 8.6.0.alpha.1", "< 9"
+
+  s.add_dependency "ffi-yajl", "~> 2.2"
+  s.add_dependency "net-ssh", "~> 2.6"
+  s.add_dependency "net-ssh-multi", "~> 1.1"
+  s.add_dependency "highline", "~> 1.6", ">= 1.6.9"
+  s.add_dependency "erubis", "~> 2.7"
+  s.add_dependency "diff-lcs", "~> 1.2", ">= 1.2.4"
+
+  s.add_dependency "chef-zero", "~> 4.2", ">= 4.2.2"
+  s.add_dependency "pry", "~> 0.9"
+
+  s.add_dependency 'plist', '~> 3.1.0'
+
+  # Audit mode requires these, so they are non-developmental dependencies now
+  %w(rspec-core rspec-expectations rspec-mocks).each { |gem| s.add_dependency gem, "~> 3.4" }
+  s.add_dependency "rspec_junit_formatter", "~> 0.2.0"
+  s.add_dependency "serverspec", "~> 2.7"
+  s.add_dependency "specinfra", "~> 2.10"
+
+  s.add_dependency "syslog-logger", "~> 1.6"
+
+  s.add_dependency "proxifier", "~> 1.0"
+
+  s.add_development_dependency "rack"
+  s.add_development_dependency "cheffish", "~> 1.1"
+
+  s.add_development_dependency "rake", "~> 10.1"
+
+  s.bindir       = "bin"
+  s.executables  = %w( chef-client chef-solo knife chef-shell chef-apply )
+
+  s.require_path = 'lib'
+  s.files = %w(Gemfile Rakefile LICENSE README.md CONTRIBUTING.md) + Dir.glob("{distro,lib,tasks,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + Dir.glob("*.gemspec")
+end
diff --git a/distro/common/man/man1/knife-cookbook-site.1 b/distro/common/man/man1/knife-cookbook-site.1
index a90a530..acfcf6b 100644
--- a/distro/common/man/man1/knife-cookbook-site.1
+++ b/distro/common/man/man1/knife-cookbook-site.1
@@ -364,17 +364,17 @@ to return something like:
 .nf
 .ft C
 apache2:
-  cookbook:              http://cookbooks.opscode.com/api/v1/cookbooks/apache2
+  cookbook:              https://supermarket.chef.io/api/v1/cookbooks/apache2
   cookbook_description:  Installs and configures apache2 using Debian symlinks with helper definitions
   cookbook_maintainer:   opscode
   cookbook_name:         apache2
 instiki:
-  cookbook:              http://cookbooks.opscode.com/api/v1/cookbooks/instiki
+  cookbook:              https://supermarket.chef.io/api/v1/cookbooks/instiki
   cookbook_description:  Installs instiki, a Ruby on Rails wiki server under passenger+Apache2.
   cookbook_maintainer:   jtimberman
   cookbook_name:         instiki
 kickstart:
-  cookbook:              http://cookbooks.opscode.com/api/v1/cookbooks/kickstart
+  cookbook:              https://supermarket.chef.io/api/v1/cookbooks/kickstart
   cookbook_description:  Creates apache2 vhost and serves a kickstart file.
   cookbook_maintainer:   opscode
   cookbook_name:         kickstart
@@ -481,18 +481,18 @@ category:        Networking
 created_at:      2009\-10\-25T23:51:07Z
 description:     Installs and configures haproxy
 external_url:
-latest_version:  http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_3
+latest_version:  https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_3
 maintainer:      opscode
 name:            haproxy
 updated_at:      2011\-06\-30T21:53:25Z
 versions:
-   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_3
-   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_2
-   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_1
-   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_0
-   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/0_8_1
-   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/0_8_0
-   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/0_7_0
+   https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_3
+   https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_2
+   https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_1
+   https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_0
+   https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/0_8_1
+   https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/0_8_0
+   https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/0_7_0
 .ft P
 .fi
 .UNINDENT
diff --git a/distro/common/markdown/man1/chef-shell.mkd b/distro/common/markdown/man1/chef-shell.mkd
index 5525ef8..216dc73 100644
--- a/distro/common/markdown/man1/chef-shell.mkd
+++ b/distro/common/markdown/man1/chef-shell.mkd
@@ -131,8 +131,8 @@ Recipe mode implements Chef's recipe DSL. Exhaustively documenting this
 DSL is outside the scope of this document. See the following pages in
 the Chef documentation for more information:
 
-  * <http://wiki.opscode.com/display/chef/Resources>
-  * <http://wiki.opscode.com/display/chef/Recipes>
+  * <http://docs.chef.io/resources.html>
+  * <http://docs.chef.io/recipes.html>
 
 Once you have defined resources in the recipe, you can trigger a
 convergence run via `run_chef`
@@ -176,7 +176,7 @@ libraries.
 ## SEE ALSO
 
   chef-client(8) knife(1)
-  <http://wiki.opscode.com/display/chef/Chef+Shell>
+  <http://docs.chef.io/ctl_chef_shell.html>
 
 ## AUTHOR
 
@@ -192,4 +192,4 @@ libraries.
 
 ## CHEF
 
-   chef-shell is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   chef-shell is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-bootstrap.mkd b/distro/common/markdown/man1/knife-bootstrap.mkd
index cb292de..a1a2d34 100644
--- a/distro/common/markdown/man1/knife-bootstrap.mkd
+++ b/distro/common/markdown/man1/knife-bootstrap.mkd
@@ -138,4 +138,4 @@ to other users via the process list using tools such as ps(1).
 
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-client.mkd b/distro/common/markdown/man1/knife-client.mkd
index e7b732e..b95a578 100644
--- a/distro/common/markdown/man1/knife-client.mkd
+++ b/distro/common/markdown/man1/knife-client.mkd
@@ -99,5 +99,5 @@ setting up a host for management with Chef.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
diff --git a/distro/common/markdown/man1/knife-configure.mkd b/distro/common/markdown/man1/knife-configure.mkd
index 507d30d..f3a4ef0 100644
--- a/distro/common/markdown/man1/knife-configure.mkd
+++ b/distro/common/markdown/man1/knife-configure.mkd
@@ -66,5 +66,5 @@ the specified _directory_.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
diff --git a/distro/common/markdown/man1/knife-cookbook-site.mkd b/distro/common/markdown/man1/knife-cookbook-site.mkd
index 9496cf1..68bc843 100644
--- a/distro/common/markdown/man1/knife-cookbook-site.mkd
+++ b/distro/common/markdown/man1/knife-cookbook-site.mkd
@@ -119,5 +119,5 @@ Uploading cookbooks to the Opscode cookbooks site:
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
diff --git a/distro/common/markdown/man1/knife-cookbook.mkd b/distro/common/markdown/man1/knife-cookbook.mkd
index deaf004..6a56059 100644
--- a/distro/common/markdown/man1/knife-cookbook.mkd
+++ b/distro/common/markdown/man1/knife-cookbook.mkd
@@ -236,7 +236,7 @@ to specify alternate files to be used on a specific OS platform or host.
 The default specificity setting is _default_, that is files in
 `COOKBOOK/files/default` will be used when a more specific copy is not
 available. Further documentation for this feature is available on the
-Chef wiki: <http://wiki.opscode.com/display/chef/File+Distribution#FileDistribution-FileSpecificity>
+Chef wiki: <https://docs.chef.io/resource_cookbook_file.html#file-specificity>
 
 Cookbooks also contain a metadata file that defines various properties
 of the cookbook. The most important of these are the _version_ and the
@@ -248,8 +248,8 @@ cookbook.
 
 ## SEE ALSO
    __knife-environment(1)__ __knife-cookbook-site(1)__
-   <http://wiki.opscode.com/display/chef/Cookbooks>
-   <http://wiki.opscode.com/display/chef/Metadata>
+   <http://docs.chef.io/cookbooks.html>
+   <http://docs.chef.io/cookbook_repo.html>
 
 ## AUTHOR
    Chef was written by Adam Jacob <adam at opscode.com> with many contributions from the community.
@@ -260,4 +260,4 @@ cookbook.
 
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-data-bag.mkd b/distro/common/markdown/man1/knife-data-bag.mkd
index 53abf95..cab28a2 100644
--- a/distro/common/markdown/man1/knife-data-bag.mkd
+++ b/distro/common/markdown/man1/knife-data-bag.mkd
@@ -117,5 +117,5 @@ encryption keys.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. http://wiki.opscode.com/display/chef/Home
+   Knife is distributed with Chef. http://docs.chef.io/
 
diff --git a/distro/common/markdown/man1/knife-environment.mkd b/distro/common/markdown/man1/knife-environment.mkd
index 98ca499..06bf423 100644
--- a/distro/common/markdown/man1/knife-environment.mkd
+++ b/distro/common/markdown/man1/knife-environment.mkd
@@ -137,8 +137,8 @@ The Ruby format of an environment is as follows:
 
 ## SEE ALSO
    __knife-node(1)__ __knife-cookbook(1)__ __knife-role(1)__
-  <http://wiki.opscode.com/display/chef/Environments>
-  <http://wiki.opscode.com/display/chef/Version+Constraints>
+  <http://docs.chef.io/environments.html>
+  <http://docs.chef.io/cookbook_versions.html>
 
 ## AUTHOR
    Chef was written by Adam Jacob <adam at opscode.com> with many contributions from the community.
@@ -148,4 +148,4 @@ The Ruby format of an environment is as follows:
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-exec.mkd b/distro/common/markdown/man1/knife-exec.mkd
index d4aa87c..1b60177 100644
--- a/distro/common/markdown/man1/knife-exec.mkd
+++ b/distro/common/markdown/man1/knife-exec.mkd
@@ -39,4 +39,4 @@ description of the commands available.
 
 ## CHEF
 
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-index.mkd b/distro/common/markdown/man1/knife-index.mkd
index 812f3fe..f1425b8 100644
--- a/distro/common/markdown/man1/knife-index.mkd
+++ b/distro/common/markdown/man1/knife-index.mkd
@@ -26,5 +26,5 @@ time for all objects to be indexed and available for search.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
diff --git a/distro/common/markdown/man1/knife-node.mkd b/distro/common/markdown/man1/knife-node.mkd
index 72b7d00..0262d64 100644
--- a/distro/common/markdown/man1/knife-node.mkd
+++ b/distro/common/markdown/man1/knife-node.mkd
@@ -126,5 +126,5 @@ When adding a recipe to a run list, there are several valid formats:
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
diff --git a/distro/common/markdown/man1/knife-role.mkd b/distro/common/markdown/man1/knife-role.mkd
index 7e0dac9..e202c52 100644
--- a/distro/common/markdown/man1/knife-role.mkd
+++ b/distro/common/markdown/man1/knife-role.mkd
@@ -70,8 +70,8 @@ run\_list.
 
 ## SEE ALSO
    __knife-node(1)__ __knife-environment(1)__
-   <http://wiki.opscode.com/display/chef/Roles>
-   <http://wiki.opscode.com/display/chef/Attributes>
+   <http://docs.chef.io/roles.html>
+   <http://docs.chef.io/attributes.html>
 
 ## AUTHOR
    Chef was written by Adam Jacob <adam at opscode.com> with many contributions from the community.
@@ -81,5 +81,5 @@ run\_list.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
diff --git a/distro/common/markdown/man1/knife-search.mkd b/distro/common/markdown/man1/knife-search.mkd
index d6729be..b289b2c 100644
--- a/distro/common/markdown/man1/knife-search.mkd
+++ b/distro/common/markdown/man1/knife-search.mkd
@@ -164,7 +164,7 @@ Find all nodes running CentOS in the production environment:
 
 ## SEE ALSO
    __knife-ssh__(1)
-   <http://wiki.opscode.com/display/chef/Attributes>
+   <http://docs.chef.io/attributes.html>
    [Lucene Query Parser Syntax](http://lucene.apache.org/java/2_3_2/queryparsersyntax.html)
 
 ## AUTHOR
@@ -175,6 +175,6 @@ Find all nodes running CentOS in the production environment:
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
 
diff --git a/distro/common/markdown/man1/knife-ssh.mkd b/distro/common/markdown/man1/knife-ssh.mkd
index 07fc594..7d37075 100644
--- a/distro/common/markdown/man1/knife-ssh.mkd
+++ b/distro/common/markdown/man1/knife-ssh.mkd
@@ -64,6 +64,6 @@ The available multiplexers are:
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
 
diff --git a/distro/common/markdown/man1/knife-status.mkd b/distro/common/markdown/man1/knife-status.mkd
index 07f0ff3..0a969e4 100644
--- a/distro/common/markdown/man1/knife-status.mkd
+++ b/distro/common/markdown/man1/knife-status.mkd
@@ -31,6 +31,6 @@ may not be publicly reachable.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
 
diff --git a/distro/common/markdown/man1/knife-tag.mkd b/distro/common/markdown/man1/knife-tag.mkd
index 6a1a2c4..b5bbb82 100644
--- a/distro/common/markdown/man1/knife-tag.mkd
+++ b/distro/common/markdown/man1/knife-tag.mkd
@@ -35,5 +35,5 @@ Lists the tags applied to _node_
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io>
 
diff --git a/distro/common/markdown/man1/knife.mkd b/distro/common/markdown/man1/knife.mkd
index c3add16..3d7c095 100644
--- a/distro/common/markdown/man1/knife.mkd
+++ b/distro/common/markdown/man1/knife.mkd
@@ -186,7 +186,7 @@ recommended though, and git fits with a lot of the workflow paradigms.
   __knife-node(1)__ __knife-recipe(1)__ __knife-role(1)__
   __knife-search(1)__ __knife-ssh(1)__ __knife-tag(1)__
 
-  Complete Chef documentation is available online: <http://wiki.opscode.com/display/chef/Home/>
+  Complete Chef documentation is available online: <http://docs.chef.io/>
 
   JSON is JavaScript Object Notation <http://json.org/>
 
@@ -209,5 +209,5 @@ recommended though, and git fits with a lot of the workflow paradigms.
    On some systems, the complete text of the Apache 2.0 License may be found in `/usr/share/common-licenses/Apache-2.0`.
 
 ## CHEF
-   Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+   Knife is distributed with Chef. <http://docs.chef.io/>
 
diff --git a/distro/common/markdown/man8/chef-client.mkd b/distro/common/markdown/man8/chef-client.mkd
index e37d283..ffe444e 100644
--- a/distro/common/markdown/man8/chef-client.mkd
+++ b/distro/common/markdown/man8/chef-client.mkd
@@ -59,8 +59,7 @@ are largely services that exist only to provide the Client with information.
 
 ## SEE ALSO
 
-Full  documentation  for  Chef  and  chef-client is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Full  documentation  for  Chef  and  chef-client is located on docs site, http://docs.chef.io/.
 
 ## AUTHOR
 
diff --git a/distro/common/markdown/man8/chef-expander.mkd b/distro/common/markdown/man8/chef-expander.mkd
index 9190a9a..a2bb7d7 100644
--- a/distro/common/markdown/man8/chef-expander.mkd
+++ b/distro/common/markdown/man8/chef-expander.mkd
@@ -67,8 +67,7 @@ See __chef-expanderctl__(8) for details.
 __chef-expanderctl__(8)
 __chef-solr__(8)
 
-Full documentation for Chef and chef-server is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Full documentation for Chef and chef-server is located on docs site, http://docs.chef.io/.
 
 ## AUTHOR
 
diff --git a/distro/common/markdown/man8/chef-expanderctl.mkd b/distro/common/markdown/man8/chef-expanderctl.mkd
index 03ce6af..db593cb 100644
--- a/distro/common/markdown/man8/chef-expanderctl.mkd
+++ b/distro/common/markdown/man8/chef-expanderctl.mkd
@@ -43,8 +43,7 @@ be restarted by the master process.
 __chef-expander-cluster__(8)
 __chef-solr__(8)
 
-Full documentation for Chef and chef-server is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Full documentation for Chef and chef-server is located on docs site, http://docs.chef.io/.
 
 ## AUTHOR
 
diff --git a/distro/common/markdown/man8/chef-server-webui.mkd b/distro/common/markdown/man8/chef-server-webui.mkd
index 977e149..b176d12 100644
--- a/distro/common/markdown/man8/chef-server-webui.mkd
+++ b/distro/common/markdown/man8/chef-server-webui.mkd
@@ -106,7 +106,7 @@ The default credentials are:
 ## SEE ALSO
 
 Full documentation for Chef and chef-server-webui (Management Console)
-is located on the Chef wiki, http://wiki.opscode.com/display/chef/Home.
+is located on the Chef docs site, http://docs.chef.io/.
 
 ## AUTHOR
 
diff --git a/distro/common/markdown/man8/chef-server.mkd b/distro/common/markdown/man8/chef-server.mkd
index 1b0f35e..46a5ea4 100644
--- a/distro/common/markdown/man8/chef-server.mkd
+++ b/distro/common/markdown/man8/chef-server.mkd
@@ -106,8 +106,7 @@ __chef-client__(8)
 __chef-server-webui__(8)
 __knife__(1)
 
-Full documentation for Chef and chef-server is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Full documentation for Chef and chef-server is located on docs site, http://docs.chef.io/.
 
 ## AUTHOR
 
diff --git a/distro/common/markdown/man8/chef-solo.mkd b/distro/common/markdown/man8/chef-solo.mkd
index 861a0fa..9d5d9a4 100644
--- a/distro/common/markdown/man8/chef-solo.mkd
+++ b/distro/common/markdown/man8/chef-solo.mkd
@@ -92,8 +92,8 @@ and use the run_list from ~/node.json.
 
 ## SEE ALSO
 
-Full documentation for Chef and  chef-solo  is  located  on  the  Chef  wiki,
-http://wiki.opscode.com/display/chef/Home.
+Full documentation for Chef and  chef-solo  is  located  on  the  Chef  docs site,
+http://docs.chef.io/.
 
 ## AUTHOR
 
diff --git a/distro/common/markdown/man8/chef-solr.mkd b/distro/common/markdown/man8/chef-solr.mkd
index 02e7d62..a210a90 100644
--- a/distro/common/markdown/man8/chef-solr.mkd
+++ b/distro/common/markdown/man8/chef-solr.mkd
@@ -75,7 +75,7 @@ when prompted for confirmation. The process should look like this:
 __chef-expander-cluster__(8)
 
 Full documentation for Chef and chef-server is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Docs site, http://docs.chef.io/.
 
 ## AUTHOR
 
diff --git a/distro/powershell/chef/chef.psm1 b/distro/powershell/chef/chef.psm1
new file mode 100644
index 0000000..6646226
--- /dev/null
+++ b/distro/powershell/chef/chef.psm1
@@ -0,0 +1,327 @@
+
+function Load-Win32Bindings {
+  Add-Type -TypeDefinition @"
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Chef
+{
+
+[StructLayout(LayoutKind.Sequential)]
+public struct PROCESS_INFORMATION
+{
+  public IntPtr hProcess;
+  public IntPtr hThread;
+  public uint dwProcessId;
+  public uint dwThreadId;
+}
+
+[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+public struct STARTUPINFO
+{
+  public uint cb;
+  public string lpReserved;
+  public string lpDesktop;
+  public string lpTitle;
+  public uint dwX;
+  public uint dwY;
+  public uint dwXSize;
+  public uint dwYSize;
+  public uint dwXCountChars;
+  public uint dwYCountChars;
+  public uint dwFillAttribute;
+  public STARTF dwFlags;
+  public ShowWindow wShowWindow;
+  public short cbReserved2;
+  public IntPtr lpReserved2;
+  public IntPtr hStdInput;
+  public IntPtr hStdOutput;
+  public IntPtr hStdError;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct SECURITY_ATTRIBUTES
+{
+  public int length;
+  public IntPtr lpSecurityDescriptor;
+  public bool bInheritHandle;
+}
+
+[Flags]
+public enum CreationFlags : int
+{
+  NONE = 0,
+  DEBUG_PROCESS = 0x00000001,
+  DEBUG_ONLY_THIS_PROCESS = 0x00000002,
+  CREATE_SUSPENDED = 0x00000004,
+  DETACHED_PROCESS = 0x00000008,
+  CREATE_NEW_CONSOLE = 0x00000010,
+  CREATE_NEW_PROCESS_GROUP = 0x00000200,
+  CREATE_UNICODE_ENVIRONMENT = 0x00000400,
+  CREATE_SEPARATE_WOW_VDM = 0x00000800,
+  CREATE_SHARED_WOW_VDM = 0x00001000,
+  CREATE_PROTECTED_PROCESS = 0x00040000,
+  EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
+  CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
+  CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
+  CREATE_DEFAULT_ERROR_MODE = 0x04000000,
+  CREATE_NO_WINDOW = 0x08000000,
+}
+
+[Flags]
+public enum STARTF : uint
+{
+  STARTF_USESHOWWINDOW = 0x00000001,
+  STARTF_USESIZE = 0x00000002,
+  STARTF_USEPOSITION = 0x00000004,
+  STARTF_USECOUNTCHARS = 0x00000008,
+  STARTF_USEFILLATTRIBUTE = 0x00000010,
+  STARTF_RUNFULLSCREEN = 0x00000020,  // ignored for non-x86 platforms
+  STARTF_FORCEONFEEDBACK = 0x00000040,
+  STARTF_FORCEOFFFEEDBACK = 0x00000080,
+  STARTF_USESTDHANDLES = 0x00000100,
+}
+
+public enum ShowWindow : short
+{
+    SW_HIDE = 0,
+    SW_SHOWNORMAL = 1,
+    SW_NORMAL = 1,
+    SW_SHOWMINIMIZED = 2,
+    SW_SHOWMAXIMIZED = 3,
+    SW_MAXIMIZE = 3,
+    SW_SHOWNOACTIVATE = 4,
+    SW_SHOW = 5,
+    SW_MINIMIZE = 6,
+    SW_SHOWMINNOACTIVE = 7,
+    SW_SHOWNA = 8,
+    SW_RESTORE = 9,
+    SW_SHOWDEFAULT = 10,
+    SW_FORCEMINIMIZE = 11,
+    SW_MAX = 11
+}
+
+public enum StandardHandle : int
+{
+  Input = -10,
+  Output = -11,
+  Error = -12
+}
+
+public static class Kernel32
+{
+  [DllImport("kernel32.dll", SetLastError=true)]
+  [return: MarshalAs(UnmanagedType.Bool)]
+  public static extern bool CreateProcess(
+    string lpApplicationName,
+    string lpCommandLine,
+    ref SECURITY_ATTRIBUTES lpProcessAttributes,
+    ref SECURITY_ATTRIBUTES lpThreadAttributes,
+    [MarshalAs(UnmanagedType.Bool)] bool bInheritHandles,
+    CreationFlags dwCreationFlags,
+    IntPtr lpEnvironment,
+    string lpCurrentDirectory,
+    ref STARTUPINFO lpStartupInfo,
+    out PROCESS_INFORMATION lpProcessInformation);
+
+  [DllImport("kernel32.dll", SetLastError=true)]
+  public static extern IntPtr GetStdHandle(
+    StandardHandle nStdHandle);
+
+  [DllImport("kernel32", SetLastError=true)]
+  public static extern int WaitForSingleObject(
+    IntPtr hHandle,
+    int dwMilliseconds);
+
+  [DllImport("kernel32", SetLastError=true)]
+  [return: MarshalAs(UnmanagedType.Bool)]
+  public static extern bool CloseHandle(
+    IntPtr hObject);
+
+  [DllImport("kernel32", SetLastError=true)]
+  [return: MarshalAs(UnmanagedType.Bool)]
+  public static extern bool GetExitCodeProcess(
+    IntPtr hProcess,
+    out int lpExitCode);
+}
+}
+"@
+}
+
+function Run-ExecutableAndWait($AppPath, $ArgumentString) {
+  # Use the Win32 API to create a new process and wait for it to terminate.
+  $null = Load-Win32Bindings
+
+  $si = New-Object Chef.STARTUPINFO
+  $pi = New-Object Chef.PROCESS_INFORMATION
+
+  $si.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($si)
+  $si.wShowWindow = [Chef.ShowWindow]::SW_SHOW
+  $si.dwFlags = [Chef.STARTF]::STARTF_USESTDHANDLES
+  $si.hStdError = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Error)
+  $si.hStdOutput = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Output)
+  $si.hStdInput = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Input)
+
+  $pSec = New-Object Chef.SECURITY_ATTRIBUTES
+  $pSec.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($pSec)
+  $pSec.bInheritHandle = $true
+  $tSec = New-Object Chef.SECURITY_ATTRIBUTES
+  $tSec.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($tSec)
+  $tSec.bInheritHandle = $true
+
+  $success = [Chef.Kernel32]::CreateProcess($AppPath, $ArgumentString, [ref] $pSec, [ref] $tSec, $true, [Chef.CreationFlags]::NONE, [IntPtr]::Zero, $pwd, [ref] $si, [ref] $pi)
+  if (-Not $success) {
+    $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+    throw "Unable to create process [$ArgumentString].  Error code $reason."
+  }
+  $waitReason = [Chef.Kernel32]::WaitForSingleObject($pi.hProcess, -1)
+  if ($waitReason -ne 0) {
+    if ($waitReason -eq -1) {
+      $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+      throw "Could not wait for process to terminate.  Error code $reason."
+    } else {
+      throw "WaitForSingleObject failed with return code $waitReason - it's impossible!"
+    }
+  }
+  $success = [Chef.Kernel32]::GetExitCodeProcess($pi.hProcess, [ref] $global:LASTEXITCODE)
+  if (-Not $success) {
+    $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+    throw "Process exit code unavailable.  Error code $reason."
+  }
+  $success = [Chef.Kernel32]::CloseHandle($pi.hProcess)
+  if (-Not $success) {
+    $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+    throw "Unable to release process handle.  Error code $reason."
+  }
+  $success = [Chef.Kernel32]::CloseHandle($pi.hThread)
+  if (-Not $success) {
+    $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+    throw "Unable to release thread handle.  Error code $reason."
+  }
+}
+
+function Get-ScriptDirectory {
+  if (!$PSScriptRoot) {
+    $Invocation = (Get-Variable MyInvocation -Scope 1).Value
+    $PSScriptRoot = Split-Path $Invocation.MyCommand.Path
+  }
+  $PSScriptRoot
+}
+
+function Run-RubyCommand($command, $argList) {
+  # This method exists to take the given list of arguments and get it past ruby's command-line
+  # interpreter unscathed and untampered.  See https://github.com/ruby/ruby/blob/trunk/win32/win32.c#L1582
+  # for a list of transformations that ruby attempts to perform with your command-line arguments
+  # before passing it onto a script.  The most important task is to defeat the globbing
+  # and wild-card expansion that ruby performs.  Note that ruby does not use MSVCRT's argc/argv
+  # and deliberately reparses the raw command-line instead.
+  #
+  # To stop ruby from interpreting command-line arguments as globs, they need to be enclosed in '
+  # Ruby doesn't allow any escape characters inside '.  This unfortunately prevents us from sending
+  # any strings which themselves contain '.  Ruby does allow multi-fragment arguments though.
+  # "foo bar"'baz qux'123"foo" is interpreted as 1 argument because there are no un-escaped
+  # whitespace there.  The argument would be interpreted as the string "foo barbaz qux123foo".
+  # This lets us escape ' characters by exiting the ' quoted string, injecting a "'" fragment and
+  # then resuming the ' quoted string again.
+  #
+  # In the process of defeating ruby, one must also defeat the helpfulness of powershell.
+  # When arguments come into this method, the standard PS rules for interpreting cmdlet arguments
+  # apply.  When using & (call operator) and providing an array of arguments, powershell (verified
+  # on PS 4.0 on Windows Server 2012R2) will not evaluate them but (contrary to documentation),
+  # it will still marginally interpret them.  The behaviour of PS 5.0 seems to be different but
+  # ignore that for now.  If any of the provided arguments has a space in it, powershell checks
+  # the first and last character to ensure that they are " characters (and that's all it checks).
+  # If they are not, it will blindly surround that argument with " characters.  It won't do this
+  # operation if no space is present, even if other special characters are present. If it notices
+  # leading and trailing " characters, it won't actually check to see if there are other "
+  # characters in the string.  Since PS 5.0 changes this behavior, we could consider using the --%
+  # "stop screwing up my arguments" operator, which is available since PS 3.0.  When encountered
+  # --% indicates that the rest of line is to be sent literally...  except if the parser encounters
+  # %FOO% cmd style environment variables.  Because reasons.  And there is no way to escape the
+  # % character in *any* waym shape or form.
+  # https://connect.microsoft.com/PowerShell/feedback/details/376207/executing-commands-which-require-quotes-and-variables-is-practically-impossible
+  #
+  # In case you think that you're either reading this incorrectly or that I'm full of shit, here
+  # are some examples.  These use EchoArgs.exe from the PowerShell Community Extensions package.
+  # I have not included the argument parsing output from EchoArgs.exe to prevent confusing you with
+  # more details about MSVCRT's parsing algorithm.
+  #
+  # $x = "foo '' bar `"baz`""
+  # & EchoArgs @($x, $x)
+  # Command line:
+  # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe"  "foo '' bar "baz"" "foo '' bar "baz""
+  #
+  # $x = "abc'123'nospace`"lulz`"!!!"
+  # & EchoArgs @($x, $x)
+  # Command line:
+  # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe"  abc'123'nospace"lulz"!!! abc'123'nospace"lulz"!!!
+  #
+  # $x = "`"`"Look ma! Tonnes of spaces! 'foo' 'bar'`"`""
+  # & EchoArgs @($x, $x)
+  # Command line:
+  # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe"  ""Look ma! Tonnes of spaces! 'foo' 'bar'"" ""Look ma! Tonnes of spaces! 'foo' 'bar'""
+  #
+  # Given all this, we can now device a strategy to work around all these immensely helpful, well
+  # documented and useful tools by looking at each incoming argument, escaping any ' characters
+  # with a '"'"' sequence, surrounding each argument with ' & joining them with a space separating
+  # them.
+  # There is another bug (https://bugs.ruby-lang.org/issues/11142) that causes ruby to mangle any
+  # "" two-character double quote sequence but since we always emit our strings inside ' except for
+  # ' characters, this should be ok.  Just remember that an argument '' should get translated to
+  # ''"'"''"'"'' on the command line.  If those intervening empty ''s are not present, the presence
+  # of "" will cause ruby to mangle that argument.
+  $transformedList = $argList | foreach { "'" + ( $_ -replace "'","'`"'`"'" ) + "'" }
+  $fortifiedArgString = $transformedList -join ' '
+
+  # Use the correct embedded ruby path.  We'll be deployed at a path that looks like
+  # [C:\opscode or some other prefix]\chef\modules\chef
+  $ruby = Join-Path (Get-ScriptDirectory)  "..\..\embedded\bin\ruby.exe"
+  $commandPath = Join-Path (Get-ScriptDirectory) "..\..\bin\$command"
+
+  Run-ExecutableAndWait $ruby """$ruby"" '$commandPath' $fortifiedArgString"
+}
+
+
+function chef-apply {
+  Run-RubyCommand 'chef-apply' $args
+}
+
+function chef-client {
+  Run-RubyCommand 'chef-client' $args
+}
+
+function chef-service-manager {
+  Run-RubyCommand 'chef-service-manager' $args
+}
+
+function chef-shell {
+  Run-RubyCommand 'chef-shell' $args
+}
+
+function chef-solo {
+  Run-RubyCommand 'chef-solo' $args
+}
+
+function chef-windows-service {
+  Run-RubyCommand 'chef-windows-service' $args
+}
+
+function knife {
+  Run-RubyCommand 'knife' $args
+}
+
+Export-ModuleMember -function chef-apply
+Export-ModuleMember -function chef-client
+Export-ModuleMember -function chef-service-manager
+Export-ModuleMember -function chef-shell
+Export-ModuleMember -function chef-solo
+Export-ModuleMember -function chef-windows-service
+Export-ModuleMember -function knife
+
+# To debug this module, uncomment the line below and then run the following.
+# Export-ModuleMember -function Run-RubyCommand
+# Remove-Module chef
+# Import-Module chef
+# "puts ARGV" | Out-File C:\opscode\chef\bin\puts_args
+# Run-RubyCommand puts_args 'Here' "are" some '"very interesting"' 'arguments[to]' "`"try out`""
diff --git a/lib/chef.rb b/lib/chef.rb
index 6bce976..1a0b802 100644
--- a/lib/chef.rb
+++ b/lib/chef.rb
@@ -31,5 +31,5 @@ require 'chef/daemon'
 require 'chef/run_status'
 require 'chef/handler'
 require 'chef/handler/json_file'
-
+require 'chef/event_dispatch/dsl'
 require 'chef/chef_class'
diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb
index ce9ceb3..b7b9f7d 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -23,7 +23,13 @@ require 'chef/mixin/from_file'
 require 'chef/mash'
 require 'chef/json_compat'
 require 'chef/search/query'
+require 'chef/server_api'
 
+# DEPRECATION NOTE
+#
+# This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1,
+# which will be moved to this namespace. New development should occur in
+# Chef::ApiClientV1 until the time before Chef 13.
 class Chef
   class ApiClient
 
@@ -143,7 +149,7 @@ class Chef
     end
 
     def self.http_api
-      Chef::REST.new(Chef::Config[:chef_server_url])
+      Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
     end
 
     def self.reregister(name)
@@ -219,7 +225,7 @@ class Chef
     end
 
     def http_api
-      @http_api ||= Chef::REST.new(Chef::Config[:chef_server_url])
+      @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
     end
 
   end
diff --git a/lib/chef/api_client/registration.rb b/lib/chef/api_client/registration.rb
index de5fc7a..7875afd 100644
--- a/lib/chef/api_client/registration.rb
+++ b/lib/chef/api_client/registration.rb
@@ -68,7 +68,8 @@ class Chef
 
       def assert_destination_writable!
         if (File.exists?(destination) && !File.writable?(destination)) or !File.writable?(File.dirname(destination))
-          raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?"
+          abs_path = File.expand_path(destination)
+          raise Chef::Exceptions::CannotWritePrivateKey, "I can't write your private key to #{abs_path} - check permissions?"
         end
       end
 
@@ -122,9 +123,13 @@ class Chef
       end
 
       def http_api
-        @http_api ||= Chef::REST.new(Chef::Config[:chef_server_url],
-                                     Chef::Config[:validation_client_name],
-                                     Chef::Config[:validation_key])
+        @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url],
+                                          {
+                                            :api_version => "0",
+                                            :client_name => Chef::Config[:validation_client_name],
+                                            :signing_key_filename => Chef::Config[:validation_key]
+                                          }
+                                         )
       end
 
       # Whether or not to generate keys locally and post the public key to the
diff --git a/lib/chef/api_client_v1.rb b/lib/chef/api_client_v1.rb
new file mode 100644
index 0000000..80f0d25
--- /dev/null
+++ b/lib/chef/api_client_v1.rb
@@ -0,0 +1,325 @@
+#
+# Author:: Adam Jacob (<adam at chef.io>)
+# Author:: Nuo Yan (<nuo at chef.io>)
+# Copyright:: Copyright (c) 2008, 2015 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/config'
+require 'chef/mixin/params_validate'
+require 'chef/mixin/from_file'
+require 'chef/mash'
+require 'chef/json_compat'
+require 'chef/search/query'
+require 'chef/exceptions'
+require 'chef/mixin/api_version_request_handling'
+require 'chef/server_api'
+require 'chef/api_client'
+
+# COMPATIBILITY NOTE
+#
+# This ApiClientV1 code attempts to make API V1 requests and falls back to
+# API V0 requests when it fails. New development should occur here instead
+# of Chef::ApiClient as this will replace that namespace when Chef 13 is released.
+#
+# If you need to default to API V0 behavior (i.e. you need GET client to return
+# a public key, etc), please use Chef::ApiClient and update your code to support
+# API V1 before you pull in Chef 13.
+class Chef
+  class ApiClientV1
+
+    include Chef::Mixin::FromFile
+    include Chef::Mixin::ParamsValidate
+    include Chef::Mixin::ApiVersionRequestHandling
+
+    SUPPORTED_API_VERSIONS = [0,1]
+
+    # Create a new Chef::ApiClientV1 object.
+    def initialize
+      @name = ''
+      @public_key = nil
+      @private_key = nil
+      @admin = false
+      @validator = false
+      @create_key = nil
+    end
+
+    def chef_rest_v0
+      @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0", :inflate_json_class => false})
+    end
+
+    def chef_rest_v1
+      @chef_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1", :inflate_json_class => false})
+    end
+
+    def self.http_api
+      Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1", :inflate_json_class => false})
+    end
+
+    # Gets or sets the client name.
+    #
+    # @params [Optional String] The name must be alpha-numeric plus - and _.
+    # @return [String] The current value of the name.
+    def name(arg=nil)
+      set_or_return(
+        :name,
+        arg,
+        :regex => /^[\-[:alnum:]_\.]+$/
+      )
+    end
+
+    # Gets or sets whether this client is an admin.
+    #
+    # @params [Optional True/False] Should be true or false - default is false.
+    # @return [True/False] The current value
+    def admin(arg=nil)
+      set_or_return(
+        :admin,
+        arg,
+        :kind_of => [ TrueClass, FalseClass ]
+      )
+    end
+
+    # Gets or sets the public key.
+    #
+    # @params [Optional String] The string representation of the public key.
+    # @return [String] The current value.
+    def public_key(arg=nil)
+      set_or_return(
+        :public_key,
+        arg,
+        :kind_of => String
+      )
+    end
+
+    # Gets or sets whether this client is a validator.
+    #
+    # @params [Boolean] whether or not the client is a validator.  If
+    #   `nil`, retrieves the already-set value.
+    # @return [Boolean] The current value
+    def validator(arg=nil)
+      set_or_return(
+        :validator,
+        arg,
+        :kind_of => [TrueClass, FalseClass]
+      )
+    end
+
+    # Private key. The server will return it as a string.
+    # Set to true under API V0 to have the server regenerate the default key.
+    #
+    # @params [Optional String] The string representation of the private key.
+    # @return [String] The current value.
+    def private_key(arg=nil)
+      set_or_return(
+        :private_key,
+        arg,
+        :kind_of => [String, TrueClass, FalseClass]
+      )
+    end
+
+    # Used to ask server to generate key pair under api V1
+    #
+    # @params [Optional True/False] Should be true or false - default is false.
+    # @return [True/False] The current value
+    def create_key(arg=nil)
+      set_or_return(
+        :create_key,
+        arg,
+        :kind_of => [ TrueClass, FalseClass ]
+      )
+    end
+
+    # The hash representation of the object. Includes the name and public_key.
+    # Private key is included if available.
+    #
+    # @return [Hash]
+    def to_hash
+      result = {
+        "name" => @name,
+        "validator" => @validator,
+        "admin" => @admin,
+        "chef_type" => "client"
+      }
+      result["private_key"] = @private_key unless @private_key.nil?
+      result["public_key"] = @public_key unless @public_key.nil?
+      result["create_key"] = @create_key unless @create_key.nil?
+      result
+    end
+
+    # The JSON representation of the object.
+    #
+    # @return [String] the JSON string.
+    def to_json(*a)
+      Chef::JSONCompat.to_json(to_hash, *a)
+    end
+
+    def self.from_hash(o)
+      client = Chef::ApiClientV1.new
+      client.name(o["name"] || o["clientname"])
+      client.admin(o["admin"])
+      client.validator(o["validator"])
+      client.private_key(o["private_key"]) if o.key?("private_key")
+      client.public_key(o["public_key"]) if o.key?("public_key")
+      client.create_key(o["create_key"]) if o.key?("create_key")
+      client
+    end
+
+    def self.from_json(j)
+      Chef::ApiClientV1.from_hash(Chef::JSONCompat.from_json(j))
+    end
+
+    def self.reregister(name)
+      api_client = Chef::ApiClientV1.load(name)
+      api_client.reregister
+    end
+
+    def self.list(inflate=false)
+      if inflate
+        response = Hash.new
+        Chef::Search::Query.new.search(:client) do |n|
+          n = self.from_hash(n) if n.instance_of?(Hash)
+          response[n.name] = n
+        end
+        response
+      else
+        http_api.get("clients")
+      end
+    end
+
+    # Load a client by name via the API
+    def self.load(name)
+      response = http_api.get("clients/#{name}")
+      Chef::ApiClientV1.from_hash(response)
+    end
+
+    # Remove this client via the REST API
+    def destroy
+      chef_rest_v1.delete("clients/#{@name}")
+    end
+
+    # Save this client via the REST API, returns a hash including the private key
+    def save
+      begin
+        update
+      rescue Net::HTTPServerException => e
+        # If that fails, go ahead and try and update it
+        if e.response.code == "404"
+          create
+        else
+          raise e
+        end
+      end
+    end
+
+    def reregister
+      # Try API V0 and if it fails due to V0 not being supported, raise the proper error message.
+      # reregister only supported in API V0 or lesser.
+      reregistered_self = chef_rest_v0.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true })
+      if reregistered_self.respond_to?(:[])
+        private_key(reregistered_self["private_key"])
+      else
+        private_key(reregistered_self.private_key)
+      end
+      self
+    rescue Net::HTTPServerException => e
+      # if there was a 406 related to versioning, give error explaining that
+      # only API version 0 is supported for reregister command
+      if e.response.code == "406" && e.response["x-ops-server-api-version"]
+        version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
+        min_version = version_header["min_version"]
+        max_version = version_header["max_version"]
+        error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
+        raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
+      else
+        raise e
+      end
+    end
+
+    # Updates the client via the REST API
+    def update
+      # NOTE: API V1 dropped support for updating client keys via update (aka PUT),
+      # but this code never supported key updating in the first place. Since
+      # it was never implemented, we will simply ignore that functionality
+      # as it is being deprecated.
+      # Delete this comment after V0 support is dropped.
+      payload = { :name => name }
+      payload[:validator] = validator unless validator.nil?
+
+      # DEPRECATION
+      # This field is ignored in API V1, but left for backwards-compat,
+      # can remove after API V0 is no longer supported.
+      payload[:admin] = admin unless admin.nil?
+
+      begin
+        new_client = chef_rest_v1.put("clients/#{name}", payload)
+      rescue Net::HTTPServerException => e
+        # rescue API V0 if 406 and the server supports V0
+        supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+        raise e unless supported_versions && supported_versions.include?(0)
+        new_client = chef_rest_v0.put("clients/#{name}", payload)
+      end
+
+      Chef::ApiClientV1.from_hash(new_client)
+    end
+
+    # Create the client via the REST API
+    def create
+      payload = {
+        :name => name,
+        :validator => validator,
+        # this field is ignored in API V1, but left for backwards-compat,
+        # can remove after OSC 11 support is finished?
+        :admin => admin
+      }
+      begin
+        # try API V1
+        raise Chef::Exceptions::InvalidClientAttribute, "You cannot set both public_key and create_key for create." if !create_key.nil? && !public_key.nil?
+
+        payload[:public_key] = public_key unless public_key.nil?
+        payload[:create_key] = create_key unless create_key.nil?
+
+        new_client = chef_rest_v1.post("clients", payload)
+
+        # get the private_key out of the chef_key hash if it exists
+        if new_client['chef_key']
+          if new_client['chef_key']['private_key']
+            new_client['private_key'] = new_client['chef_key']['private_key']
+          end
+          new_client['public_key'] = new_client['chef_key']['public_key']
+          new_client.delete('chef_key')
+        end
+
+      rescue Net::HTTPServerException => e
+        # rescue API V0 if 406 and the server supports V0
+        supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+        raise e unless supported_versions && supported_versions.include?(0)
+
+        # under API V0, a key pair will always be created unless public_key is
+        # passed on initial POST
+        payload[:public_key] = public_key unless public_key.nil?
+
+        new_client = chef_rest_v0.post("clients", payload)
+      end
+      Chef::ApiClientV1.from_hash(self.to_hash.merge(new_client))
+    end
+
+    # As a string
+    def to_s
+      "client[#{@name}]"
+    end
+
+  end
+end
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 297e46e..0ee201f 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -1,7 +1,7 @@
 #
 # Author:: AJ Christensen (<aj at opscode.com>)
 # Author:: Mark Mzyk (mmzyk at opscode.com)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,7 +17,6 @@
 # limitations under the License.
 
 require 'pp'
-require 'uri'
 require 'socket'
 require 'chef/config'
 require 'chef/config_fetcher'
@@ -47,7 +46,6 @@ class Chef
     def reconfigure
       configure_chef
       configure_logging
-      configure_proxy_environment_variables
       configure_encoding
       emit_warnings
     end
@@ -85,6 +83,7 @@ class Chef
     def configure_chef
       parse_options
       load_config_file
+      Chef::Config.export_proxies
     end
 
     # Parse the config file
@@ -93,7 +92,6 @@ class Chef
       if config[:config_file].nil?
         Chef::Log.warn("No config file found or specified on command line, using command line options.")
       elsif config_fetcher.config_missing?
-        pp config_missing: true
         Chef::Log.warn("*****************************************")
         Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
         Chef::Log.warn("*****************************************")
@@ -181,14 +179,6 @@ class Chef
       end
     end
 
-    # Configure and set any proxy environment variables according to the config.
-    def configure_proxy_environment_variables
-      configure_http_proxy
-      configure_https_proxy
-      configure_ftp_proxy
-      configure_no_proxy
-    end
-
     # Sets the default external encoding to UTF-8 (users can change this, but they shouldn't)
     def configure_encoding
       Encoding.default_external = Chef::Config[:ruby_encoding]
@@ -303,79 +293,6 @@ class Chef
       Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2)
     end
 
-    # Set ENV['http_proxy']
-    def configure_http_proxy
-      if http_proxy = Chef::Config[:http_proxy]
-        http_proxy_string = configure_proxy("http", http_proxy,
-          Chef::Config[:http_proxy_user], Chef::Config[:http_proxy_pass])
-        env['http_proxy'] = http_proxy_string unless env['http_proxy']
-        env['HTTP_PROXY'] = http_proxy_string unless env['HTTP_PROXY']
-      end
-    end
-
-    # Set ENV['https_proxy']
-    def configure_https_proxy
-      if https_proxy = Chef::Config[:https_proxy]
-        https_proxy_string = configure_proxy("https", https_proxy,
-          Chef::Config[:https_proxy_user], Chef::Config[:https_proxy_pass])
-        env['https_proxy'] = https_proxy_string unless env['https_proxy']
-        env['HTTPS_PROXY'] = https_proxy_string unless env['HTTPS_PROXY']
-      end
-    end
-
-    # Set ENV['ftp_proxy']
-    def configure_ftp_proxy
-      if ftp_proxy = Chef::Config[:ftp_proxy]
-        ftp_proxy_string = configure_proxy("ftp", ftp_proxy,
-          Chef::Config[:ftp_proxy_user], Chef::Config[:ftp_proxy_pass])
-        env['ftp_proxy'] = ftp_proxy_string unless env['ftp_proxy']
-        env['FTP_PROXY'] = ftp_proxy_string unless env['FTP_PROXY']
-      end
-    end
-
-    # Set ENV['no_proxy']
-    def configure_no_proxy
-      if Chef::Config[:no_proxy]
-        env['no_proxy'] = Chef::Config[:no_proxy] unless env['no_proxy']
-        env['NO_PROXY'] = Chef::Config[:no_proxy] unless env['NO_PROXY']
-      end
-    end
-
-    # Builds a proxy uri. Examples:
-    #   http://username:password@hostname:port
-    #   https://username@hostname:port
-    #   ftp://hostname:port
-    # when
-    #   scheme = "http", "https", or "ftp"
-    #   hostport = hostname:port
-    #   user = username
-    #   pass = password
-    def configure_proxy(scheme, path, user, pass)
-      begin
-        path = "#{scheme}://#{path}" unless path.include?('://')
-        # URI.split returns the following parts:
-        # [scheme, userinfo, host, port, registry, path, opaque, query, fragment]
-        parts = URI.split(URI.encode(path))
-        # URI::Generic.build requires an integer for the port, but URI::split gives
-        # returns a string for the port.
-        parts[3] = parts[3].to_i if parts[3]
-        if user
-          userinfo = URI.encode(URI.encode(user), '@:')
-          if pass
-            userinfo << ":#{URI.encode(URI.encode(pass), '@:')}"
-          end
-          parts[1] = userinfo
-        end
-
-        return URI::Generic.build(parts).to_s
-      rescue URI::Error => e
-        # URI::Error messages generally include the offending string. Including a message
-        # for which proxy config item has the issue should help deduce the issue when
-        # the URI::Error message is vague.
-        raise Chef::Exceptions::BadProxyURI, "Cannot configure #{scheme} proxy. Does not comply with URI scheme. #{e.message}"
-      end
-    end
-
     # This is a hook for testing
     def env
       ENV
@@ -383,7 +300,7 @@ class Chef
 
     def emit_warnings
       if Chef::Config[:chef_gem_compile_time]
-        Chef::Log.deprecation "setting chef_gem_compile_time to true is deprecated"
+        Chef.log_deprecation "setting chef_gem_compile_time to true is deprecated"
       end
     end
 
@@ -395,6 +312,7 @@ class Chef
 
         Chef::FileCache.store("chef-stacktrace.out", chef_stacktrace_out)
         Chef::Log.fatal("Stacktrace dumped to #{Chef::FileCache.load("chef-stacktrace.out", false)}")
+        Chef::Log.fatal("Please provide the contents of the stacktrace.out file if you file a bug report")
         Chef::Log.debug(message)
         true
       end
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb
index e9768b2..6a371c8 100644
--- a/lib/chef/application/apply.rb
+++ b/lib/chef/application/apply.rb
@@ -29,7 +29,7 @@ require 'chef/resources'
 
 class Chef::Application::Apply < Chef::Application
 
-  banner "Usage: chef-apply [RECIPE_FILE] [-e RECIPE_TEXT] [-s]"
+  banner "Usage: chef-apply [RECIPE_FILE | -e RECIPE_TEXT | -s] [OPTIONS]"
 
   option :execute,
     :short        => "-e RECIPE_TEXT",
@@ -49,6 +49,24 @@ class Chef::Application::Apply < Chef::Application
     :description => "Load attributes from a JSON file or URL",
     :proc => nil
 
+  option :force_logger,
+    :long         => "--force-logger",
+    :description  => "Use logger output instead of formatter output",
+    :boolean      => true,
+    :default      => false
+
+  option :force_formatter,
+    :long         => "--force-formatter",
+    :description  => "Use formatter output instead of logger output",
+    :boolean      => true,
+    :default      => false
+
+  option :formatter,
+    :short        => "-F FORMATTER",
+    :long         => "--format FORMATTER",
+    :description  => "output format to use",
+    :proc         => lambda { |format| Chef::Config.add_formatter(format) }
+
   option :log_level,
     :short        => "-l LEVEL",
     :long         => "--log_level LEVEL",
@@ -79,10 +97,16 @@ class Chef::Application::Apply < Chef::Application
     :description  => 'Enable whyrun mode',
     :boolean      => true
 
+  option :profile_ruby,
+    :long         => "--[no-]profile-ruby",
+    :description  => "Dump complete Ruby call graph stack of entire Chef run (expert only)",
+    :boolean      => true,
+    :default      => false
+
   option :color,
     :long         => '--[no-]color',
     :boolean      => true,
-    :default      => !Chef::Platform.windows?,
+    :default      => true,
     :description  => "Use colored output, defaults to enabled"
 
   option :minimal_ohai,
@@ -100,7 +124,7 @@ class Chef::Application::Apply < Chef::Application
     parse_options
     Chef::Config.merge!(config)
     configure_logging
-    configure_proxy_environment_variables
+    Chef::Config.export_proxies
     parse_json
   end
 
@@ -172,6 +196,7 @@ class Chef::Application::Apply < Chef::Application
     ensure
       @recipe_fh.close
     end
+    Chef::Platform::Rebooter.reboot_if_needed!(runner)
   end
 
   def run_application
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index a5faee9..8cc045c 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -2,7 +2,7 @@
 # Author:: AJ Christensen (<aj at opscode.com)
 # Author:: Christopher Brown (<cb at opscode.com>)
 # Author:: Mark Mzyk (mmzyk at opscode.com)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -55,11 +55,17 @@ class Chef::Application::Client < Chef::Application
     :boolean      => true,
     :default      => false
 
+  option :profile_ruby,
+    :long         => "--[no-]profile-ruby",
+    :description  => "Dump complete Ruby call graph stack of entire Chef run (expert only)",
+    :boolean      => true,
+    :default      => false
+
   option :color,
     :long         => '--[no-]color',
     :boolean      => true,
-    :default      => !Chef::Platform.windows?,
-    :description  => "Use colored output, defaults to false on Windows, true otherwise"
+    :default      => true,
+    :description  => "Use colored output, defaults to enabled"
 
   option :log_level,
     :short        => "-l LEVEL",
@@ -160,6 +166,11 @@ class Chef::Application::Client < Chef::Application
     :description  => "Set the client key file location",
     :proc         => nil
 
+  option :named_run_list,
+    :short        => "-n NAMED_RUN_LIST",
+    :long         => "--named-run-list NAMED_RUN_LIST",
+    :description  => "Use a policyfile's named run list instead of the default run list"
+
   option :environment,
     :short        => '-E ENVIRONMENT',
     :long         => '--environment ENVIRONMENT',
@@ -279,6 +290,12 @@ class Chef::Application::Client < Chef::Application
     Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
 
     Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+
+    if Chef::Config.has_key?(:chef_repo_path) && Chef::Config.chef_repo_path.nil?
+      Chef::Config.delete(:chef_repo_path)
+      Chef::Log.warn "chef_repo_path was set in a config file but was empty. Assuming #{Chef::Config.chef_repo_path}"
+    end
+
     if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
       Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
     end
@@ -320,12 +337,6 @@ class Chef::Application::Client < Chef::Application
       unless expected_modes.include?(mode)
         Chef::Application.fatal!(unrecognized_audit_mode(mode))
       end
-
-      unless mode == :disabled
-        # This should be removed when audit-mode is enabled by default/no longer
-        # an experimental feature.
-        Chef::Log.warn(audit_mode_experimental_message)
-      end
     end
   end
 
@@ -448,26 +459,15 @@ class Chef::Application::Client < Chef::Application
     "\nEnable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options."
   end
 
-  def audit_mode_settings_explaination
-    "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `:audit_mode = :enabled` in your config file." +
-    "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `:audit_mode = :disabled` in your config file." +
-    "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `:audit_mode = :audit_only` in your config file." +
+  def audit_mode_settings_explanation
+    "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `audit_mode :enabled` in your config file." +
+    "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `audit_mode :disabled` in your config file." +
+    "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `audit_mode :audit_only` in your config file." +
     "\nAudit mode is disabled by default."
   end
 
   def unrecognized_audit_mode(mode)
-    "Unrecognized setting #{mode} for audit mode." + audit_mode_settings_explaination
-  end
-
-  def audit_mode_experimental_message
-    msg = if Chef::Config[:audit_mode] == :audit_only
-      "Chef-client has been configured to skip converge and only audit."
-    else
-      "Chef-client has been configured to audit after it converges."
-    end
-    msg += " Audit mode is an experimental feature currently under development. API changes may occur. Use at your own risk."
-    msg += audit_mode_settings_explaination
-    return msg
+    "Unrecognized setting #{mode} for audit mode." + audit_mode_settings_explanation
   end
 
   def fetch_recipe_tarball(url, path)
diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb
index af5216a..d169a5d 100644
--- a/lib/chef/application/knife.rb
+++ b/lib/chef/application/knife.rb
@@ -44,8 +44,8 @@ class Chef::Application::Knife < Chef::Application
   option :color,
     :long         => '--[no-]color',
     :boolean      => true,
-    :default      => !Chef::Platform.windows?,
-    :description  => "Use colored output, defaults to false on Windows, true otherwise"
+    :default      => true,
+    :description  => "Use colored output, defaults to enabled"
 
   option :environment,
     :short        => "-E ENVIRONMENT",
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index dd09d65..4b472e9 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -1,7 +1,7 @@
 #
 # Author:: AJ Christensen (<aj at opscode.com>)
 # Author:: Mark Mzyk (mmzyk at opscode.com)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -52,6 +52,12 @@ class Chef::Application::Solo < Chef::Application
     :boolean      => true,
     :default      => false
 
+  option :profile_ruby,
+    :long         => "--[no-]profile-ruby",
+    :description  => "Dump complete Ruby call graph stack of entire Chef run (expert only)",
+    :boolean      => true,
+    :default      => false
+
   option :color,
     :long         => '--[no-]color',
     :boolean      => true,
@@ -214,7 +220,7 @@ class Chef::Application::Solo < Chef::Application
       FileUtils.mkdir_p(recipes_path)
       tarball_path = File.join(recipes_path, 'recipes.tgz')
       fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path)
-      Chef::Mixin::Command.run_command(:command => "tar zxvf #{tarball_path} -C #{recipes_path}")
+      Mixlib::ShellOut.new("tar zxvf #{tarball_path} -C #{recipes_path}").run_command
     end
 
     # json_attribs shuld be fetched after recipe_url tarball is unpacked.
diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb
index b42a01c..2f93805 100644
--- a/lib/chef/application/windows_service.rb
+++ b/lib/chef/application/windows_service.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Christopher Maier (<maier at lambda.local>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2011-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -45,8 +45,7 @@ class Chef
       option :log_location,
         :short        => "-L LOGLOCATION",
         :long         => "--logfile LOGLOCATION",
-        :description  => "Set the log file location",
-        :default => "#{ENV['SYSTEMDRIVE']}/chef/client.log"
+        :description  => "Set the log file location"
 
       option :splay,
         :short        => "-s SECONDS",
@@ -60,6 +59,8 @@ class Chef
         :description  => "Set the number of seconds to wait between chef-client runs",
         :proc         => lambda { |s| s.to_i }
 
+      DEFAULT_LOG_LOCATION ||= "#{ENV['SYSTEMDRIVE']}/chef/client.log"
+
       def service_init
         @service_action_mutex = Mutex.new
         @service_signal = ConditionVariable.new
@@ -187,9 +188,18 @@ class Chef
           # Pass config params to the new process
           config_params = " --no-fork"
           config_params += " -c #{Chef::Config[:config_file]}" unless  Chef::Config[:config_file].nil?
-          config_params += " -L #{Chef::Config[:log_location]}" unless Chef::Config[:log_location] == STDOUT
+          # log_location might be an event logger and if so we cannot pass as a command argument
+          # but shed no tears! If the logger is an event logger, it must have been configured
+          # as such in the config file and chef-client will use that when no arg is passed here
+          config_params += " -L #{resolve_log_location}" if resolve_log_location.is_a?(String)
+          
           # Starts a new process and waits till the process exits
-          result = shell_out("chef-client #{config_params}", :timeout => Chef::Config[:windows_service][:watchdog_timeout])
+
+          result = shell_out(
+            "chef-client #{config_params}",
+            :timeout => Chef::Config[:windows_service][:watchdog_timeout],
+            :logger => Chef::Log
+          )
           Chef::Log.debug "#{result.stdout}"
           Chef::Log.debug "#{result.stderr}"
         rescue Mixlib::ShellOut::CommandTimeout => e
@@ -231,7 +241,7 @@ class Chef
       # See application.rb for related comments.
 
       def configure_logging
-        Chef::Log.init(MonoLogger.new(Chef::Config[:log_location]))
+        Chef::Log.init(MonoLogger.new(resolve_log_location))
         if want_additional_logger?
           configure_stdout_logger
         end
@@ -260,6 +270,11 @@ class Chef
         Chef::Config[:log_level] == :auto
       end
 
+      def resolve_log_location
+        # STDOUT is the default log location, but makes no sense for a windows service
+        Chef::Config[:log_location] == STDOUT ? DEFAULT_LOG_LOCATION : Chef::Config[:log_location]
+      end
+
       # if log_level is `:auto`, convert it to :warn (when using output formatter)
       # or :info (no output formatter). See also +using_output_formatter?+
       def resolve_log_level
diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb
index de8ed65..d969896 100644
--- a/lib/chef/application/windows_service_manager.rb
+++ b/lib/chef/application/windows_service_manager.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Seth Chisamore (<schisamo at opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2011-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -51,8 +51,7 @@ class Chef
       option :log_location,
         :short        => "-L LOGLOCATION",
         :long         => "--logfile LOGLOCATION",
-        :description  => "Set the log file location for chef-service",
-        :default => "#{ENV['SYSTEMDRIVE']}/chef/client.log"
+        :description  => "Set the log file location for chef-service"
 
       option :help,
         :short        => "-h",
@@ -78,7 +77,7 @@ class Chef
 
         raise ArgumentError, "Service definition is not provided" if service_options.nil?
 
-        required_options = [:service_name, :service_display_name, :service_name, :service_description, :service_file_path]
+        required_options = [:service_name, :service_display_name, :service_description, :service_file_path]
 
         required_options.each do |req_option|
           if !service_options.has_key?(req_option)
@@ -92,6 +91,8 @@ class Chef
         @service_file_path = service_options[:service_file_path]
         @service_start_name = service_options[:run_as_user]
         @password = service_options[:run_as_password]
+        @delayed_start = service_options[:delayed_start]
+        @dependencies = service_options[:dependencies]
       end
 
       def run(params = ARGV)
@@ -113,17 +114,22 @@ class Chef
             cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR)
 
             ::Win32::Service.new(
-                                 :service_name     => @service_name,
-                                 :display_name     => @service_display_name,
-                                 :description      => @service_description,
-                                 # Prior to 0.8.5, win32-service creates interactive services by default,
-                                 # and we don't want that, so we need to override the service type.
-                                 :service_type     => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS,
-                                 :start_type       => ::Win32::Service::SERVICE_AUTO_START,
-                                 :binary_path_name => cmd,
-                                 :service_start_name => @service_start_name,
-                                 :password => @password,
-                                 )
+              :service_name       => @service_name,
+              :display_name       => @service_display_name,
+              :description        => @service_description,
+              # Prior to 0.8.5, win32-service creates interactive services by default,
+              # and we don't want that, so we need to override the service type.
+              :service_type       => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS,
+              :start_type         => ::Win32::Service::SERVICE_AUTO_START,
+              :binary_path_name   => cmd,
+              :service_start_name => @service_start_name,
+              :password           => @password,
+              :dependencies       => @dependencies
+            )
+            ::Win32::Service.configure(
+              :service_name     => @service_name,
+              :delayed_start    => @delayed_start
+            ) unless @delayed_start.nil?
             puts "Service '#{@service_name}' has successfully been installed."
           end
         when 'status'
diff --git a/lib/chef/audit/audit_reporter.rb b/lib/chef/audit/audit_reporter.rb
index a4f84ed..d952d8a 100644
--- a/lib/chef/audit/audit_reporter.rb
+++ b/lib/chef/audit/audit_reporter.rb
@@ -34,6 +34,7 @@ class Chef
         @rest_client = rest_client
         # Ruby 1.9.3 and above "enumerate their values in the order that the corresponding keys were inserted."
         @ordered_control_groups = Hash.new
+        @audit_phase_error = nil
       end
 
       def run_context
@@ -46,7 +47,7 @@ class Chef
         @run_status = run_status
       end
 
-      def audit_phase_complete
+      def audit_phase_complete(audit_output)
         Chef::Log.debug("Audit Reporter completed successfully without errors.")
         ordered_control_groups.each do |name, control_group|
           audit_data.add_control_group(control_group)
@@ -57,8 +58,9 @@ class Chef
       # that runs tests - normal errors are interpreted as EXAMPLE failures and captured.
       # We still want to send available audit information to the server so we process the
       # known control groups.
-      def audit_phase_failed(error)
+      def audit_phase_failed(error, audit_output)
         # The stacktrace information has already been logged elsewhere
+        @audit_phase_error = error
         Chef::Log.debug("Audit Reporter failed.")
         ordered_control_groups.each do |name, control_group|
           audit_data.add_control_group(control_group)
@@ -70,7 +72,9 @@ class Chef
       end
 
       def run_failed(error)
-        post_auditing_data(error)
+        # Audit phase errors are captured when audit_phase_failed gets called.
+        # The error passed here isn't relevant to auditing, so we ignore it.
+        post_auditing_data
       end
 
       def control_group_started(name)
@@ -98,7 +102,7 @@ class Chef
 
       private
 
-      def post_auditing_data(error = nil)
+      def post_auditing_data
         unless auditing_enabled?
           Chef::Log.debug("Audit Reports are disabled. Skipping sending reports.")
           return
@@ -116,8 +120,10 @@ class Chef
         Chef::Log.debug("Sending audit report (run-id: #{audit_data.run_id})")
         run_data = audit_data.to_hash
 
-        if error
-          run_data[:error] = "#{error.class.to_s}: #{error.message}\n#{error.backtrace.join("\n")}"
+        if @audit_phase_error
+          error_info = "#{@audit_phase_error.class}: #{@audit_phase_error.message}"
+          error_info << "\n#{@audit_phase_error.backtrace.join("\n")}" if @audit_phase_error.backtrace
+          run_data[:error] = error_info
         end
 
         Chef::Log.debug "Audit Report:\n#{Chef::JSONCompat.to_json_pretty(run_data)}"
@@ -163,7 +169,6 @@ class Chef
       def iso8601ify(time)
         time.utc.iso8601.to_s
       end
-
     end
   end
 end
diff --git a/spec/support/lib/chef/resource/zen_follower.rb b/lib/chef/audit/logger.rb
similarity index 66%
copy from spec/support/lib/chef/resource/zen_follower.rb
copy to lib/chef/audit/logger.rb
index ddc289e..e46f54e 100644
--- a/spec/support/lib/chef/resource/zen_follower.rb
+++ b/lib/chef/audit/logger.rb
@@ -15,25 +15,21 @@
 # limitations under the License.
 #
 
-require 'chef/knife'
-require 'chef/json_compat'
+require 'stringio'
 
 class Chef
-  class Resource
-    class ZenFollower < Chef::Resource
+  class Audit
+    class Logger
+      def self.puts(message="")
+        @buffer ||= StringIO.new
+        @buffer.puts(message)
 
-      provides :follower, platform: "zen"
-
-      def initialize(name, run_context=nil)
-        @resource_name = :zen_follower
-        super
+        Chef::Log.info(message)
       end
 
-      def master(arg=nil)
-        if !arg.nil?
-          @master = arg
-        end
-        @master
+      def self.read_buffer
+        return "" if @buffer.nil?
+        @buffer.string
       end
     end
   end
diff --git a/lib/chef/audit/runner.rb b/lib/chef/audit/runner.rb
index 801bf5e..a3bf0b6 100644
--- a/lib/chef/audit/runner.rb
+++ b/lib/chef/audit/runner.rb
@@ -16,6 +16,8 @@
 # limitations under the License.
 #
 
+require 'chef/audit/logger'
+
 class Chef
   class Audit
     class Runner
@@ -104,6 +106,7 @@ class Chef
         RSpec.configure do |c|
           c.color = Chef::Config[:color]
           c.expose_dsl_globally = false
+          c.project_source_dirs = Array(Chef::Config[:cookbook_path])
           c.backtrace_exclusion_patterns << exclusion_pattern
         end
       end
@@ -115,8 +118,8 @@ class Chef
       # the output stream to be changed for a formatter once the formatter has
       # been added.
       def set_streams
-        RSpec.configuration.output_stream = Chef::Config[:log_location]
-        RSpec.configuration.error_stream = Chef::Config[:log_location]
+        RSpec.configuration.output_stream = Chef::Audit::Logger
+        RSpec.configuration.error_stream = Chef::Audit::Logger
       end
 
       # Add formatters which we use to
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
index d3f7ee5..5ccb8bb 100644
--- a/lib/chef/chef_class.rb
+++ b/lib/chef/chef_class.rb
@@ -26,6 +26,11 @@
 # injected" into this class by other objects and do not reference the class symbols in those files
 # directly and we do not need to require those files here.
 
+require 'chef/platform/provider_priority_map'
+require 'chef/platform/resource_priority_map'
+require 'chef/platform/provider_handler_map'
+require 'chef/platform/resource_handler_map'
+
 class Chef
   class << self
 
@@ -33,50 +38,81 @@ class Chef
     # Public API
     #
 
+    #
     # Get the node object
     #
     # @return [Chef::Node] node object of the chef-client run
+    #
     attr_reader :node
 
+    #
     # Get the run context
     #
     # @return [Chef::RunContext] run_context of the chef-client run
+    #
     attr_reader :run_context
 
+    # Register an event handler with user specified block
+    #
+    # @return[Chef::EventDispatch::Base] handler object
+    def event_handler(&block)
+      dsl = Chef::EventDispatch::DSL.new('Chef client DSL')
+      dsl.instance_eval(&block)
+    end
+
     # Get the array of providers associated with a resource_name for the current node
     #
     # @param resource_name [Symbol] name of the resource as a symbol
+    #
     # @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
+    #
     def get_provider_priority_array(resource_name)
-      @provider_priority_map.get_priority_array(node, resource_name).dup
+      result = provider_priority_map.get_priority_array(node, resource_name.to_sym)
+      result = result.dup if result
+      result
     end
 
+    #
     # Get the array of resources associated with a resource_name for the current node
     #
     # @param resource_name [Symbol] name of the resource as a symbol
+    #
     # @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
+    #
     def get_resource_priority_array(resource_name)
-      @resource_priority_map.get_priority_array(node, resource_name).dup
+      result = resource_priority_map.get_priority_array(node, resource_name.to_sym)
+      result = result.dup if result
+      result
     end
 
+    #
     # Set the array of providers associated with a resource_name for the current node
     #
     # @param resource_name [Symbol] name of the resource as a symbol
-    # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
+    # @param priority_array [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node
     # @param filter [Hash] Chef::Nodearray-style filter
+    #
     # @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node
-    def set_provider_priority_array(resource_name, priority_array, *filter)
-      @provider_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
+    #
+    def set_provider_priority_array(resource_name, priority_array, *filter, &block)
+      result = provider_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
+      result = result.dup if result
+      result
     end
 
+    #
     # Get the array of resources associated with a resource_name for the current node
     #
     # @param resource_name [Symbol] name of the resource as a symbol
-    # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
+    # @param priority_array [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node
     # @param filter [Hash] Chef::Nodearray-style filter
+    #
     # @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node
-    def set_resource_priority_array(resource_name, priority_array, *filter)
-      @resource_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
+    #
+    def set_resource_priority_array(resource_name, priority_array, *filter, &block)
+      result = resource_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
+      result = result.dup if result
+      result
     end
 
     #
@@ -85,22 +121,27 @@ class Chef
     #   *NOT* for public consumption ]
     #
 
+    #
     # Sets the resource_priority_map
     #
-    # @api private
     # @param resource_priority_map [Chef::Platform::ResourcePriorityMap]
+    #
+    # @api private
     def set_resource_priority_map(resource_priority_map)
       @resource_priority_map = resource_priority_map
     end
 
+    #
     # Sets the provider_priority_map
     #
-    # @api private
     # @param provider_priority_map [Chef::Platform::providerPriorityMap]
+    #
+    # @api private
     def set_provider_priority_map(provider_priority_map)
       @provider_priority_map = provider_priority_map
     end
 
+    #
     # Sets the node object
     #
     # @api private
@@ -109,14 +150,17 @@ class Chef
       @node = node
     end
 
+    #
     # Sets the run_context object
     #
-    # @api private
     # @param run_context [Chef::RunContext]
+    #
+    # @api private
     def set_run_context(run_context)
       @run_context = run_context
     end
 
+    #
     # Resets the internal state
     #
     # @api private
@@ -125,6 +169,57 @@ class Chef
       @node = nil
       @provider_priority_map = nil
       @resource_priority_map = nil
+      @provider_handler_map = nil
+      @resource_handler_map = nil
+    end
+
+    # @api private
+    def provider_priority_map
+      # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
+      @provider_priority_map ||= Chef::Platform::ProviderPriorityMap.instance
+    end
+    # @api private
+    def resource_priority_map
+      @resource_priority_map ||= Chef::Platform::ResourcePriorityMap.instance
+    end
+    # @api private
+    def provider_handler_map
+      @provider_handler_map ||= Chef::Platform::ProviderHandlerMap.instance
+    end
+    # @api private
+    def resource_handler_map
+      @resource_handler_map ||= Chef::Platform::ResourceHandlerMap.instance
+    end
+
+    #
+    # Emit a deprecation message.
+    #
+    # @param message The message to send.
+    # @param location The location. Defaults to the caller who called you (since
+    #   generally the person who triggered the check is the one that needs to be
+    #   fixed).
+    #
+    # @example
+    #     Chef.deprecation("Deprecated!")
+    #
+    # @api private this will likely be removed in favor of an as-yet unwritten
+    #      `Chef.log`
+    def log_deprecation(message, location=nil)
+      location ||= Chef::Log.caller_location
+      # `run_context.events` is the primary deprecation target if we're in a
+      # run. If we are not yet in a run, print to `Chef::Log`.
+      if run_context && run_context.events
+        run_context.events.deprecation(message, location)
+      else
+        Chef::Log.deprecation(message, location)
+      end
     end
   end
+
+  # @api private Only for test dependency injection; not evenly implemented as yet.
+  def self.path_to(path)
+    path
+  end
+
+  reset!
 end
diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb
index 4084fb8..4d07135 100644
--- a/lib/chef/chef_fs/chef_fs_data_store.rb
+++ b/lib/chef/chef_fs/chef_fs_data_store.rb
@@ -66,12 +66,65 @@ class Chef
     #   - ChefFSDataStore lets cookbooks be uploaded into a temporary memory
     #     storage, and when the cookbook is committed, copies the files onto the
     #     disk in the correct place (/cookbooks/apache2/recipes/default.rb).
+    #
     # 3. Data bags:
     #   - The Chef server expects data bags in /data/BAG/ITEM
     #   - The repository stores data bags in /data_bags/BAG/ITEM
     #
     # 4. JSON filenames are generally NAME.json in the repository (e.g. /nodes/foo.json).
     #
+    # 5. Org membership:
+    #    chef-zero stores user membership in an org as a series of empty files.
+    #    If an org has jkeiser and cdoherty as members, chef-zero expects these
+    #    files to exist:
+    #
+    #    - `users/jkeiser` (content: '{}')
+    #    - `users/cdoherty` (content: '{}')
+    #
+    #    ChefFS, on the other hand, stores user membership in an org as a single
+    #    file, `members.json`, with content:
+    #
+    #        ```json
+    #        [
+    #          { "user": { "username": "jkeiser" } },
+    #          { "user": { "username": "cdoherty" } }
+    #        ]
+    #        ```
+    #
+    #    To translate between the two, we need to intercept requests to `users`
+    #    like so:
+    #
+    #    - `list(users)` -> `get(/members.json)`
+    #    - `get(users/NAME)` -> `get(/members.json)`, see if it's in there
+    #    - `create(users/NAME)` -> `get(/members.json)`, add name, `set(/members.json)`
+    #    - `delete(users/NAME)` -> `get(/members.json)`, remove name, `set(/members.json)`
+    #
+    # 6. Org invitations:
+    #    chef-zero stores org membership invitations as a series of empty files.
+    #    If an org has invited jkeiser and cdoherty (and they have not yet accepted
+    #    the invite), chef-zero expects these files to exist:
+    #
+    #    - `association_requests/jkeiser` (content: '{}')
+    #    - `association_requests/cdoherty` (content: '{}')
+    #
+    #    ChefFS, on the other hand, stores invitations as a single file,
+    #    `invitations.json`, with content:
+    #
+    #        ```json
+    #        [
+    #          { "id" => "jkeiser-chef", 'username' => 'jkeiser' },
+    #          { "id" => "cdoherty-chef", 'username' => 'cdoherty' }
+    #        ]
+    #        ```
+    #
+    #    To translate between the two, we need to intercept requests to `users`
+    #    like so:
+    #
+    #    - `list(association_requests)` -> `get(/invitations.json)`
+    #    - `get(association_requests/NAME)` -> `get(/invitations.json)`, see if it's in there
+    #    - `create(association_requests/NAME)` -> `get(/invitations.json)`, add name, `set(/invitations.json)`
+    #    - `delete(association_requests/NAME)` -> `get(/invitations.json)`, remove name, `set(/invitations.json)`
+    #
     class ChefFSDataStore
       #
       # Create a new ChefFSDataStore
@@ -83,9 +136,10 @@ class Chef
       #   Generally will be a +ChefFS::FileSystem::ChefRepositoryFileSystemRoot+
       #   object, created from +ChefFS::Config.local_fs+.
       #
-      def initialize(chef_fs)
+      def initialize(chef_fs, chef_config=Chef::Config)
         @chef_fs = chef_fs
         @memory_store = ChefZero::DataStore::MemoryStore.new
+        @repo_mode = chef_config[:repo_mode]
       end
 
       def publish_description
@@ -93,6 +147,7 @@ class Chef
       end
 
       attr_reader :chef_fs
+      attr_reader :repo_mode
 
       def create_dir(path, name, *options)
         if use_memory_store?(path)
@@ -108,6 +163,24 @@ class Chef
         end
       end
 
+      #
+      # If you want to get the contents of /data/x/y from the server,
+      # you say chef_fs.child('data').child('x').child('y').read.
+      # It will make exactly one network request: GET /data/x/y
+      # And that will return 404 if it doesn't exist.
+      #
+      # ChefFS objects do not go to the network until you ask them for data.
+      # This means you can construct a /data/x/y ChefFS entry early.
+      #
+      # Alternative:
+      # chef_fs.child('data') could have done a GET /data preemptively,
+      # allowing it to know whether child('x') was valid (GET /data gives you
+      # a list of data bags). Then child('x') could have done a GET /data/x,
+      # allowing it to know whether child('y') (the item) existed. Finally,
+      # we would do the GET /data/x/y to read the contents. Three network
+      # requests instead of 1.
+      #
+
       def create(path, name, data, *options)
         if use_memory_store?(path)
           @memory_store.create(path, name, data, *options)
@@ -115,6 +188,32 @@ class Chef
         elsif path[0] == 'cookbooks' && path.length == 2
           # Do nothing.  The entry gets created when the cookbook is created.
 
+        # create [/organizations/ORG]/users/NAME (with content '{}')
+        # Manipulate the `members.json` file that contains a list of all users
+        elsif is_org? && path == [ 'users' ]
+          update_json('members.json', []) do |members|
+            # Format of each entry: { "user": { "username": "jkeiser" } }
+            if members.any? { |member| member['user']['username'] == name }
+              raise ChefZero::DataStore::DataAlreadyExistsError.new(path, entry)
+            end
+
+            # Actually add the user
+            members << { "user" => { "username" => name } }
+          end
+
+        # create [/organizations/ORG]/association_requests/NAME (with content '{}')
+        # Manipulate the `invitations.json` file that contains a list of all users
+        elsif is_org? && path == [ 'association_requests' ]
+          update_json('invitations.json', []) do |invitations|
+            # Format of each entry: { "id" => "jkeiser-chef", 'username' => 'jkeiser' }
+            if invitations.any? { |member| member['username'] == name }
+              raise ChefZero::DataStore::DataAlreadyExistsError.new(path)
+            end
+
+            # Actually add the user (TODO insert org name??)
+            invitations << { "username" => name }
+          end
+
         else
           if !data.is_a?(String)
             raise "set only works with strings"
@@ -142,6 +241,24 @@ class Chef
             raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
           end
 
+        # GET [/organizations/ORG]/users/NAME -> /users/NAME
+        # Manipulates members.json
+        elsif is_org? && path[0] == 'users' && path.length == 2
+          if get_json('members.json', []).any? { |member| member['user']['username'] == path[1] }
+            '{}'
+          else
+            raise ChefZero::DataStore::DataNotFoundError.new(path)
+          end
+
+        # GET [/organizations/ORG]/association_requests/NAME -> /users/NAME
+        # Manipulates invites.json
+        elsif is_org? && path[0] == 'association_requests' && path.length == 2
+          if get_json('invites.json', []).any? { |member| member['user']['username'] == path[1] }
+            '{}'
+          else
+            raise ChefZero::DataStore::DataNotFoundError.new(path)
+          end
+
         else
           with_entry(path) do |entry|
             if path[0] == 'cookbooks' && path.length == 3
@@ -209,6 +326,29 @@ class Chef
       def delete(path)
         if use_memory_store?(path)
           @memory_store.delete(path)
+
+        # DELETE [/organizations/ORG]/users/NAME
+        # Manipulates members.json
+        elsif is_org? && path[0] == 'users' && path.length == 2
+          update_json('members.json', []) do |members|
+            result = members.reject { |member| member['user']['username'] == path[1] }
+            if result.size == members.size
+              raise ChefZero::DataStore::DataNotFoundError.new(path)
+            end
+            result
+          end
+
+        # DELETE [/organizations/ORG]/users/NAME
+        # Manipulates members.json
+        elsif is_org? && path[0] == 'association_requests' && path.length == 2
+          update_json('invitations.json', []) do |invitations|
+            result = invitations.reject { |invitation| invitation['username'] == path[1] }
+            if result.size == invitations.size
+              raise ChefZero::DataStore::DataNotFoundError.new(path)
+            end
+            result
+          end
+
         else
           with_entry(path) do |entry|
             begin
@@ -394,9 +534,22 @@ class Chef
               end
             end
           end
+
+        elsif path[0] == 'acls'
+          # /acls/containers|nodes|.../x.json
+          # /acls/organization.json
+          if path.length == 3 || path == [ 'acls', 'organization' ]
+            path = path.dup
+            path[-1] = "#{path[-1]}.json"
+          end
+
+          # /acls/containers|nodes|... do NOT drop into the next elsif, and do
+          # not get .json appended
+
+        # /nodes|clients|.../x.json
         elsif path.length == 2
           path = path.dup
-          path[1] = "#{path[1]}.json"
+          path[-1] = "#{path[-1]}.json"
         end
         path
       end
@@ -477,6 +630,32 @@ class Chef
         metadata = ChefZero::CookbookData.metadata_from(dir, path[1], nil, [])
         metadata[:version] || '0.0.0'
       end
+
+      def update_json(path, default_value)
+        entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path)
+        begin
+          input = Chef::JSONCompat.parse(entry.read)
+          output = yield input.dup
+          entry.write(Chef::JSONCompat.to_json_pretty(output)) if output != input
+        rescue Chef::ChefFS::FileSystem::NotFoundError
+          # Send the default value to the caller, and create the entry if the caller updates it
+          output = yield default_value
+          entry.parent.create_child(entry.name, Chef::JSONCompat.to_json_pretty(output)) if output != []
+        end
+      end
+
+      def get_json(path, default_value)
+        entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path)
+        begin
+          Chef::JSONCompat.parse(entry.read)
+        rescue Chef::ChefFS::FileSystem::NotFoundError
+          default_value
+        end
+      end
+
+      def is_org?
+        repo_mode == 'hosted_everything'
+      end
     end
   end
 end
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
index 6666a3d..40cbb36 100644
--- a/lib/chef/chef_fs/config.rb
+++ b/lib/chef/chef_fs/config.rb
@@ -111,7 +111,7 @@ class Chef
       #
       def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}, ui = nil)
         @chef_config = chef_config
-        @cwd = cwd
+        @cwd = File.expand_path(cwd)
         @cookbook_version = options[:cookbook_version]
 
         if @chef_config[:repo_mode] == 'everything' && is_hosted? && !ui.nil?
@@ -166,34 +166,37 @@ class Chef
       # server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah'
       # server_path('/home/*/chef_repo/cookbooks/blah') == nil
       #
-      # If there are multiple paths (cookbooks, roles, data bags, etc. can all
-      # have separate paths), and cwd+the path reaches into one of them, we will
-      # return a path relative to that.  Otherwise we will return a path to
-      # chef_repo.
+      # If there are multiple different, manually specified paths to object locations
+      # (cookbooks, roles, data bags, etc. can all have separate paths), and cwd+the
+      # path reaches into one of them, we will return a path relative to the first
+      # one to match it.  Otherwise we expect the path provided to be to the chef
+      # repo path itself.  Paths that are not available on the server are not supported.
       #
       # Globs are allowed as well, but globs outside server paths are NOT
       # (presently) supported.  See above examples.  TODO support that.
       #
       # If the path does not reach into ANY specified directory, nil is returned.
       def server_path(file_path)
-        pwd = File.expand_path(Dir.pwd)
-        absolute_pwd = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd))
+        target_path = Chef::ChefFS::PathUtils.realest_path(file_path, @cwd)
 
         # Check all object paths (cookbooks_dir, data_bags_dir, etc.)
+        # These are either manually specified by the user or autogenerated relative
+        # to chef_repo_path.
         object_paths.each_pair do |name, paths|
           paths.each do |path|
-            realest_path = Chef::ChefFS::PathUtils.realest_path(path)
-            if PathUtils.descendant_of?(absolute_pwd, realest_path)
-              relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_pwd, realest_path)
-              return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
+            object_abs_path = Chef::ChefFS::PathUtils.realest_path(path, @cwd)
+            if relative_path = PathUtils.descendant_path(target_path, object_abs_path)
+              return Chef::ChefFS::PathUtils.join("/#{name}", relative_path)
             end
           end
         end
 
         # Check chef_repo_path
         Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path|
-          realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path)
-          if absolute_pwd == realest_chef_repo_path
+          # We're using realest_path here but we really don't need to - we can just expand the
+          # path and use realpath because a repo_path if provided *must* exist.
+          realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path, @cwd)
+          if Chef::ChefFS::PathUtils.os_path_eq?(target_path, realest_chef_repo_path)
             return '/'
           end
         end
@@ -201,15 +204,10 @@ class Chef
         nil
       end
 
-      # The current directory, relative to server root
+      # The current directory, relative to server root.  This is a case-sensitive server path.
+      # It only exists if the current directory is a child of one of the recognized object_paths below.
       def base_path
-        @base_path ||= begin
-          if @chef_config[:chef_repo_path]
-            server_path(File.expand_path(@cwd))
-          else
-            nil
-          end
-        end
+        @base_path ||= server_path(@cwd)
       end
 
       # Print the given server path, relative to the current directory
@@ -217,10 +215,10 @@ class Chef
         server_path = entry.path
         if base_path && server_path[0,base_path.length] == base_path
           if server_path == base_path
-            return "."
-          elsif server_path[base_path.length,1] == "/"
+            return '.'
+          elsif server_path[base_path.length,1] == '/'
             return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
-          elsif base_path == "/" && server_path[0,1] == "/"
+          elsif base_path == '/' && server_path[0,1] == '/'
             return server_path[1, server_path.length - 1]
           end
         end
diff --git a/lib/chef/chef_fs/data_handler/client_data_handler.rb b/lib/chef/chef_fs/data_handler/client_data_handler.rb
index d81f35e..5bcbd4e 100644
--- a/lib/chef/chef_fs/data_handler/client_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/client_data_handler.rb
@@ -13,11 +13,13 @@ class Chef
             'validator' => false,
             'chef_type' => 'client'
           }
+          # Handle the fact that admin/validator have changed type from string -> boolean
+          client['admin'] = (client['admin'] == 'true') if client['admin'].is_a?(String)
+          client['validator'] = (client['validator'] == 'true') if client['validator'].is_a?(String)
           if entry.respond_to?(:org) && entry.org
             defaults['orgname'] = entry.org
           end
           result = normalize_hash(client, defaults)
-          # You can NOT send json_class, or it will fail
           result.delete('json_class')
           result
         end
diff --git a/lib/chef/chef_fs/file_pattern.rb b/lib/chef/chef_fs/file_pattern.rb
index 134d22c..b2351da 100644
--- a/lib/chef/chef_fs/file_pattern.rb
+++ b/lib/chef/chef_fs/file_pattern.rb
@@ -72,7 +72,7 @@ class Chef
       def could_match_children?(path)
         return false if path == '' # Empty string is not a path
 
-        argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+        argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
         return false if is_absolute != argument_is_absolute
         path = path[1,path.length-1] if argument_is_absolute
 
@@ -111,7 +111,7 @@ class Chef
       #
       # This method assumes +could_match_children?(path)+ is +true+.
       def exact_child_name_under(path)
-        path = path[1,path.length-1] if !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+        path = path[1,path.length-1] if Chef::ChefFS::PathUtils::is_absolute?(path)
         dirs_in_path = Chef::ChefFS::PathUtils::split(path).length
         return nil if exact_parts.length <= dirs_in_path
         return exact_parts[dirs_in_path]
@@ -149,7 +149,7 @@ class Chef
       #   abc/*/def.match?('abc/foo/def') == true
       #   abc/*/def.match?('abc/foo') == false
       def match?(path)
-        argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+        argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
         return false if is_absolute != argument_is_absolute
         path = path[1,path.length-1] if argument_is_absolute
         !!regexp.match(path)
@@ -160,17 +160,6 @@ class Chef
         pattern
       end
 
-      # Given a relative file pattern and a directory, makes a new file pattern
-      # starting with the directory.
-      #
-      #   FilePattern.relative_to('/usr/local', 'bin/*grok') == FilePattern.new('/usr/local/bin/*grok')
-      #
-      # BUG: this does not support patterns starting with <tt>..</tt>
-      def self.relative_to(dir, pattern)
-        return FilePattern.new(pattern) if pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/
-        FilePattern.new(Chef::ChefFS::PathUtils::join(dir, pattern))
-      end
-
     private
 
       def regexp
@@ -195,7 +184,7 @@ class Chef
 
       def calculate
         if !@regexp
-          @is_absolute = !!(@pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+          @is_absolute = Chef::ChefFS::PathUtils::is_absolute?(@pattern)
 
           full_regexp_parts = []
           normalized_parts = []
diff --git a/lib/chef/chef_fs/file_system/acl_dir.rb b/lib/chef/chef_fs/file_system/acl_dir.rb
index c2354d4..9f68d7c 100644
--- a/lib/chef/chef_fs/file_system/acl_dir.rb
+++ b/lib/chef/chef_fs/file_system/acl_dir.rb
@@ -28,10 +28,9 @@ class Chef
           parent.parent.child(name).api_path
         end
 
-        def child(name)
+        def make_child_entry(name, exists = nil)
           result = @children.select { |child| child.name == name }.first if @children
-          result ||= can_have_child?(name, false) ?
-                     AclEntry.new(name, self) : NonexistentFSObject.new(name, self)
+          result || AclEntry.new(name, self, exists)
         end
 
         def can_have_child?(name, is_dir)
@@ -42,7 +41,7 @@ class Chef
           if @children.nil?
             # Grab the ACTUAL children (/nodes, /containers, etc.) and get their names
             names = parent.parent.child(name).children.map { |child| child.dir? ? "#{child.name}.json" : child.name }
-            @children = names.map { |name| AclEntry.new(name, self, true) }
+            @children = names.map { |name| make_child_entry(name, true) }
           end
           @children
         end
diff --git a/lib/chef/chef_fs/file_system/acls_dir.rb b/lib/chef/chef_fs/file_system/acls_dir.rb
index 938bf73..a8c6372 100644
--- a/lib/chef/chef_fs/file_system/acls_dir.rb
+++ b/lib/chef/chef_fs/file_system/acls_dir.rb
@@ -40,8 +40,12 @@ class Chef
           parent.api_path
         end
 
+        def make_child_entry(name)
+          children.select { |child| child.name == name }.first
+        end
+
         def can_have_child?(name, is_dir)
-          is_dir ? ENTITY_TYPES.include(name) : name == 'organization.json'
+          is_dir ? ENTITY_TYPES.include?(name) : name == 'organization.json'
         end
 
         def children
diff --git a/lib/chef/chef_fs/file_system/base_fs_dir.rb b/lib/chef/chef_fs/file_system/base_fs_dir.rb
index 8cc277f..47e33f9 100644
--- a/lib/chef/chef_fs/file_system/base_fs_dir.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_dir.rb
@@ -31,11 +31,6 @@ class Chef
           true
         end
 
-        # Override child(name) to provide a child object by name without the network read
-        def child(name)
-          children.select { |child| child.name == name }.first || NonexistentFSObject.new(name, self)
-        end
-
         def can_have_child?(name, is_dir)
           true
         end
diff --git a/lib/chef/chef_fs/file_system/base_fs_object.rb b/lib/chef/chef_fs/file_system/base_fs_object.rb
index 43e6a51..916ab82 100644
--- a/lib/chef/chef_fs/file_system/base_fs_object.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_object.rb
@@ -95,7 +95,10 @@ class Chef
         # directly perform a network request to retrieve the y.json data bag.  No
         # network request was necessary to retrieve
         def child(name)
-          NonexistentFSObject.new(name, self)
+          if can_have_child?(name, true) || can_have_child?(name, false)
+            result = make_child_entry(name)
+          end
+          result || NonexistentFSObject.new(name, self)
         end
 
         # Override children to report your *actual* list of children as an array.
@@ -171,7 +174,7 @@ class Chef
 
         # Important directory attributes: name, parent, path, root
         # Overridable attributes: dir?, child(name), path_for_printing
-        # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to
+        # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to, make_child_entry
       end # class BaseFsObject
     end
   end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
index a7f1d73..4391bdb 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
@@ -58,14 +58,7 @@ class Chef
         end
 
         def children
-          begin
-            Dir.entries(file_path).sort.
-                select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
-                map { |child_name| make_child(child_name) }.
-                select { |entry| !(entry.dir? && entry.children.size == 0) }
-          rescue Errno::ENOENT
-            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
-          end
+          super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
         end
 
         def can_have_child?(name, is_dir)
@@ -99,7 +92,7 @@ class Chef
 
         protected
 
-        def make_child(child_name)
+        def make_child_entry(child_name)
           segment_info = CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {}
           ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive])
         end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
index 66709cc..914412f 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
@@ -34,14 +34,7 @@ class Chef
         attr_reader :recursive
 
         def children
-          begin
-            Dir.entries(file_path).sort.
-                select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
-                map { |child_name| make_child(child_name) }.
-                select { |entry| !(entry.dir? && entry.children.size == 0) }
-          rescue Errno::ENOENT
-            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
-          end
+          super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
         end
 
         def can_have_child?(name, is_dir)
@@ -78,7 +71,7 @@ class Chef
 
         protected
 
-        def make_child(child_name)
+        def make_child_entry(child_name)
           ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive)
         end
       end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
index 7c60b51..5b49566 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
@@ -37,21 +37,14 @@ class Chef
         attr_reader :chefignore
 
         def children
-          begin
-            Dir.entries(file_path).sort.
-                select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
-                map { |child_name| make_child(child_name) }.
-                select do |entry|
-                  # empty cookbooks and cookbook directories are ignored
-                  if !entry.can_upload?
-                    Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
-                    false
-                  else
-                    true
-                  end
-                end
-          rescue Errno::ENOENT
-            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+          super.select do |entry|
+            # empty cookbooks and cookbook directories are ignored
+            if !entry.can_upload?
+              Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
+              false
+            else
+              true
+            end
           end
         end
 
@@ -61,7 +54,7 @@ class Chef
 
         def write_cookbook(cookbook_path, cookbook_version_json, from_fs)
           cookbook_name = File.basename(cookbook_path)
-          child = make_child(cookbook_name)
+          child = make_child_entry(cookbook_name)
 
           # Use the copy/diff algorithm to copy it down so we don't destroy
           # chefignored data.  This is terribly un-thread-safe.
@@ -80,7 +73,7 @@ class Chef
 
         protected
 
-        def make_child(child_name)
+        def make_child_entry(child_name)
           ChefRepositoryFileSystemCookbookDir.new(child_name, self)
         end
       end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
index 0b14750..39172e7 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
@@ -70,20 +70,9 @@ class Chef
           Chef::JSONCompat.to_json_pretty(object)
         end
 
-        def children
-          # Except cookbooks and data bag dirs, all things must be json files
-          begin
-            Dir.entries(file_path).sort.
-                select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
-                map { |child_name| make_child(child_name) }
-          rescue Errno::ENOENT
-            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
-          end
-        end
-
         protected
 
-        def make_child(child_name)
+        def make_child_entry(child_name)
           ChefRepositoryFileSystemEntry.new(child_name, self)
         end
       end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
index d03baf9..267fe30 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
@@ -68,13 +68,13 @@ class Chef
         attr_reader :child_paths
         attr_reader :versioned_cookbooks
 
-        CHILDREN = %w(invitations.json members.json org.json)
+        CHILDREN = %w(org.json invitations.json members.json)
 
         def children
           @children ||= begin
-            result = child_paths.keys.sort.map { |name| make_child_entry(name) }.select { |child| !child.nil? }
-            result += root_dir.children.select { |c| CHILDREN.include?(c.name) } if root_dir
-            result.sort_by { |c| c.name }
+            result = child_paths.keys.sort.map { |name| make_child_entry(name) }
+            result += CHILDREN.map { |name| make_child_entry(name) }
+            result.select { |c| c && c.exists? }.sort_by { |c| c.name }
           end
         end
 
@@ -149,19 +149,23 @@ class Chef
         # cookbooks from all of them when you list or grab them).
         #
         def make_child_entry(name)
-          paths = child_paths[name].select do |path|
-            File.exists?(path)
+          if CHILDREN.include?(name)
+            return nil if !root_dir
+            return root_dir.child(name)
           end
+
+          paths = (child_paths[name] || []).select { |path| File.exists?(path) }
           if paths.size == 0
-            return nil
+            return NonexistentFSObject.new(name, self)
           end
-          if name == 'cookbooks'
+          case name
+          when 'cookbooks'
             dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
-          elsif name == 'data_bags'
+          when 'data_bags'
             dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) }
-          elsif name == 'policies'
+          when 'policies'
             dirs = paths.map { |path| ChefRepositoryFileSystemPoliciesDir.new(name, self, path) }
-          elsif name == 'acls'
+          when 'acls'
             dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) }
           else
             data_handler = case name
diff --git a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
index 370308e..a243e0a 100644
--- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
@@ -90,11 +90,11 @@ class Chef
         end
 
         def rest
-          Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true)
+          Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true, :api_version => "0")
         end
 
         def get_json(path)
-          Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key).get(path)
+          Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :api_version => "0").get(path)
         end
 
         def chef_rest
@@ -110,7 +110,8 @@ class Chef
         end
 
         def can_have_child?(name, is_dir)
-          is_dir && children.any? { |child| child.name == name }
+          result = children.select { |child| child.name == name }.first
+          result && !!result.dir? == !!is_dir
         end
 
         def org
@@ -119,11 +120,16 @@ class Chef
             if File.dirname(path) == '/organizations'
               File.basename(path)
             else
-              nil
+              # In Chef 12, everything is in an org.
+              'chef'
             end
           end
         end
 
+        def make_child_entry(name)
+          children.select { |child| child.name == name }.first
+        end
+
         def children
           @children ||= begin
             result = [
diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb
index 03652dc..c0f0390 100644
--- a/lib/chef/chef_fs/file_system/cookbook_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb
@@ -16,6 +16,7 @@
 # limitations under the License.
 #
 
+require 'chef/chef_fs/command_line'
 require 'chef/chef_fs/file_system/rest_list_dir'
 require 'chef/chef_fs/file_system/cookbook_subdir'
 require 'chef/chef_fs/file_system/cookbook_file'
@@ -71,16 +72,15 @@ class Chef
           "#{parent.api_path}/#{cookbook_name}/#{version || "_latest"}"
         end
 
-        def child(name)
+        def make_child_entry(name)
           # Since we're ignoring the rules and doing a network request here,
           # we need to make sure we don't rethrow the exception.  (child(name)
           # is not supposed to fail.)
           begin
-            result = children.select { |child| child.name == name }.first
-            return result if result
+            children.select { |child| child.name == name }.first
           rescue Chef::ChefFS::FileSystem::NotFoundError
+            nil
           end
-          return NonexistentFSObject.new(name, self)
         end
 
         def can_have_child?(name, is_dir)
diff --git a/lib/chef/chef_fs/file_system/cookbook_subdir.rb b/lib/chef/chef_fs/file_system/cookbook_subdir.rb
index 73c709e..e7a6d3b 100644
--- a/lib/chef/chef_fs/file_system/cookbook_subdir.rb
+++ b/lib/chef/chef_fs/file_system/cookbook_subdir.rb
@@ -45,6 +45,11 @@ class Chef
           true
         end
 
+        def make_child_entry(name)
+          result = @children.select { |child| child.name == name }.first if @children
+          result || NonexistentFSObject.new(name, self)
+        end
+
         def rest
           parent.rest
         end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
index d6246f1..560ceb4 100644
--- a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
@@ -31,7 +31,7 @@ class Chef
         def children
           if @children.nil?
             names = parent.parent.child(name).children.map { |child| "#{child.cookbook_name}.json" }
-            @children = names.uniq.map { |name| AclEntry.new(name, self, true) }
+            @children = names.uniq.map { |name| make_child_entry(name, true) }
           end
           @children
         end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
index 27bedd3..6f49c28 100644
--- a/lib/chef/chef_fs/file_system/cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
@@ -36,17 +36,9 @@ class Chef
           super("cookbooks", parent)
         end
 
-        def child(name)
-          if @children
-            result = self.children.select { |child| child.name == name }.first
-            if result
-              result
-            else
-              NonexistentFSObject.new(name, self)
-            end
-          else
-            CookbookDir.new(name, self)
-          end
+        def make_child_entry(name)
+          result = @children.select { |child| child.name == name }.first if @children
+          result || CookbookDir.new(name, self)
         end
 
         def children
diff --git a/lib/chef/chef_fs/file_system/data_bags_dir.rb b/lib/chef/chef_fs/file_system/data_bags_dir.rb
index 6d0685d..1cb61bb 100644
--- a/lib/chef/chef_fs/file_system/data_bags_dir.rb
+++ b/lib/chef/chef_fs/file_system/data_bags_dir.rb
@@ -27,16 +27,14 @@ class Chef
           super("data_bags", parent, "data")
         end
 
-        def child(name)
+        def make_child_entry(name, exists = false)
           result = @children.select { |child| child.name == name }.first if @children
-          result || DataBagDir.new(name, self)
+          result || DataBagDir.new(name, self, exists)
         end
 
         def children
           begin
-            @children ||= root.get_json(api_path).keys.sort.map do |entry|
-              DataBagDir.new(entry, self, true)
-            end
+            @children ||= root.get_json(api_path).keys.sort.map { |entry| make_child_entry(entry, true) }
           rescue Timeout::Error => e
             raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout getting children: #{e}"
           rescue Net::HTTPServerException => e
diff --git a/lib/chef/chef_fs/file_system/environments_dir.rb b/lib/chef/chef_fs/file_system/environments_dir.rb
index 559dd6a..3aee3ee 100644
--- a/lib/chef/chef_fs/file_system/environments_dir.rb
+++ b/lib/chef/chef_fs/file_system/environments_dir.rb
@@ -30,7 +30,7 @@ class Chef
           super("environments", parent, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new)
         end
 
-        def _make_child_entry(name, exists = nil)
+        def make_child_entry(name, exists = nil)
           if name == '_default.json'
             DefaultEnvironmentEntry.new(name, self, exists)
           else
diff --git a/lib/chef/chef_fs/file_system/file_system_entry.rb b/lib/chef/chef_fs/file_system/file_system_entry.rb
index 1af7e61..5ce8b33 100644
--- a/lib/chef/chef_fs/file_system/file_system_entry.rb
+++ b/lib/chef/chef_fs/file_system/file_system_entry.rb
@@ -40,15 +40,18 @@ class Chef
         end
 
         def children
+          # Except cookbooks and data bag dirs, all things must be json files
           begin
-            Dir.entries(file_path).sort.select { |entry| entry != '.' && entry != '..' }.map { |entry| make_child(entry) }
+            Dir.entries(file_path).sort.
+                map { |child_name| make_child_entry(child_name) }.
+                select { |child| child && can_have_child?(child.name, child.dir?) }
           rescue Errno::ENOENT
             raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
           end
         end
 
         def create_child(child_name, file_contents=nil)
-          child = make_child(child_name)
+          child = make_child_entry(child_name)
           if child.exists?
             raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
           end
@@ -69,18 +72,22 @@ class Chef
         end
 
         def delete(recurse)
-          if dir?
-            if !recurse
-              raise MustDeleteRecursivelyError.new(self, $!)
+          begin
+            if dir?
+              if !recurse
+                raise MustDeleteRecursivelyError.new(self, $!)
+              end
+              FileUtils.rm_r(file_path)
+            else
+              File.delete(file_path)
             end
-            FileUtils.rm_rf(file_path)
-          else
-            File.delete(file_path)
+          rescue Errno::ENOENT
+            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
           end
         end
 
         def exists?
-          File.exists?(file_path)
+          File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?))
         end
 
         def read
@@ -99,7 +106,7 @@ class Chef
 
         protected
 
-        def make_child(child_name)
+        def make_child_entry(child_name)
           FileSystemEntry.new(child_name, self)
         end
       end
diff --git a/lib/chef/chef_fs/file_system/memory_dir.rb b/lib/chef/chef_fs/file_system/memory_dir.rb
index a7eda3c..260a916 100644
--- a/lib/chef/chef_fs/file_system/memory_dir.rb
+++ b/lib/chef/chef_fs/file_system/memory_dir.rb
@@ -1,5 +1,4 @@
 require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/nonexistent_fs_object'
 require 'chef/chef_fs/file_system/memory_file'
 
 class Chef
@@ -13,8 +12,8 @@ class Chef
 
         attr_reader :children
 
-        def child(name)
-          @children.select { |child| child.name == name }.first || Chef::ChefFS::FileSystem::NonexistentFSObject.new(name, self)
+        def make_child_entry(name)
+          @children.select { |child| child.name == name }.first
         end
 
         def add_child(child)
diff --git a/lib/chef/chef_fs/file_system/multiplexed_dir.rb b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
index 06d4af7..70b827f 100644
--- a/lib/chef/chef_fs/file_system/multiplexed_dir.rb
+++ b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
@@ -35,6 +35,21 @@ class Chef
           end
         end
 
+        def make_child_entry(name)
+          result = nil
+          multiplexed_dirs.each do |dir|
+            child_entry = dir.child(name)
+            if child_entry.exists?
+              if result
+                Chef::Log.warn("Child with name '#{child_entry.name}' found in multiple directories: #{result.parent.path_for_printing} and #{child_entry.parent.path_for_printing}")
+              else
+                result = child_entry
+              end
+            end
+          end
+          result
+        end
+
         def can_have_child?(name, is_dir)
           write_dir.can_have_child?(name, is_dir)
         end
diff --git a/lib/chef/chef_fs/file_system/nodes_dir.rb b/lib/chef/chef_fs/file_system/nodes_dir.rb
index c3c4837..2610b06 100644
--- a/lib/chef/chef_fs/file_system/nodes_dir.rb
+++ b/lib/chef/chef_fs/file_system/nodes_dir.rb
@@ -33,7 +33,7 @@ class Chef
         def children
           begin
             @children ||= root.get_json(env_api_path).keys.sort.map do |key|
-              _make_child_entry("#{key}.json", true)
+              make_child_entry("#{key}.json", true)
             end
           rescue Timeout::Error => e
             raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
diff --git a/lib/chef/chef_fs/file_system/organization_members_entry.rb b/lib/chef/chef_fs/file_system/organization_members_entry.rb
index 94393b3..40042a9 100644
--- a/lib/chef/chef_fs/file_system/organization_members_entry.rb
+++ b/lib/chef/chef_fs/file_system/organization_members_entry.rb
@@ -39,9 +39,9 @@ class Chef
           members = minimize_value(_read_json)
           (desired_members - members).each do |member|
             begin
-              rest.post(File.join(api_path, member), {})
+              rest.post(api_path, 'username' => member)
             rescue Net::HTTPServerException => e
-              if e.response.code == '404'
+              if %w(404 405).include?(e.response.code)
                 raise "Chef server at #{api_path} does not allow you to directly add members.  Please either upgrade your Chef server or move the users you want into invitations.json instead of members.json."
               else
                 raise
diff --git a/lib/chef/chef_fs/file_system/rest_list_dir.rb b/lib/chef/chef_fs/file_system/rest_list_dir.rb
index 672fa44..0ac735a 100644
--- a/lib/chef/chef_fs/file_system/rest_list_dir.rb
+++ b/lib/chef/chef_fs/file_system/rest_list_dir.rb
@@ -33,12 +33,6 @@ class Chef
         attr_reader :api_path
         attr_reader :data_handler
 
-        def child(name)
-          result = @children.select { |child| child.name == name }.first if @children
-          result ||= can_have_child?(name, false) ?
-                     _make_child_entry(name) : NonexistentFSObject.new(name, self)
-        end
-
         def can_have_child?(name, is_dir)
           name =~ /\.json$/ && !is_dir
         end
@@ -46,7 +40,7 @@ class Chef
         def children
           begin
             @children ||= root.get_json(api_path).keys.sort.map do |key|
-              _make_child_entry("#{key}.json", true)
+              make_child_entry("#{key}.json", true)
             end
           rescue Timeout::Error => e
             raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
@@ -66,7 +60,7 @@ class Chef
             raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Parse error reading JSON creating child '#{name}': #{e}"
           end
 
-          result = _make_child_entry(name, true)
+          result = make_child_entry(name, true)
 
           if data_handler
             object = data_handler.normalize_for_post(object, result)
@@ -106,7 +100,8 @@ class Chef
           parent.rest
         end
 
-        def _make_child_entry(name, exists = nil)
+        def make_child_entry(name, exists = nil)
+          @children.select { |child| child.name == name }.first if @children
           RestListEntry.new(name, self, exists)
         end
       end
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
index 86872da..9101e45 100644
--- a/lib/chef/chef_fs/knife.rb
+++ b/lib/chef/chef_fs/knife.rb
@@ -17,6 +17,7 @@
 #
 
 require 'chef/knife'
+require 'pathname'
 
 class Chef
   module ChefFS
@@ -63,7 +64,7 @@ class Chef
         # --chef-repo-path forcibly overrides all other paths
         if config[:chef_repo_path]
           Chef::Config[:chef_repo_path] = config[:chef_repo_path]
-          %w(acl client cookbook container data_bag environment group node role user).each do |variable_name|
+          Chef::ChefFS::Config::INFLECTIONS.each_value do |variable_name|
             Chef::Config.delete("#{variable_name}_path".to_sym)
           end
         end
@@ -98,14 +99,41 @@ class Chef
       end
 
       def pattern_arg_from(arg)
-        # TODO support absolute file paths and not just patterns?  Too much?
-        # Could be super useful in a world with multiple repo paths
-        if !@chef_fs_config.base_path && !Chef::ChefFS::PathUtils.is_absolute?(arg)
-          # Check if chef repo path is specified to give a better error message
-          ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path")
+        inferred_path = nil
+        if Chef::ChefFS::PathUtils.is_absolute?(arg)
+          # We should be able to use this as-is - but the user might have incorrectly provided
+          # us with a path that is based off of the OS root path instead of the Chef-FS root.
+          # Do a quick and dirty sanity check.
+          if possible_server_path = @chef_fs_config.server_path(arg)
+            ui.warn("The absolute path provided is suspicious: #{arg}")
+            ui.warn("If you wish to refer to a file location, please provide a path that is rooted at the chef-repo.")
+            ui.warn("Consider writing '#{possible_server_path}' instead of '#{arg}'")
+          end
+          # Use the original path because we can't be sure.
+          inferred_path = arg
+        elsif arg[0,1] == '~'
+          # Let's be nice and fix it if possible - but warn the user.
+          ui.warn("A path relative to a user home directory has been provided: #{arg}")
+          ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+          inferred_path = @chef_fs_config.server_path(arg)
+          ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+        elsif Pathname.new(arg).absolute?
+          # It is definitely a system absolute path (such as C:\ or \\foo\bar) but it cannot be
+          # interpreted as a Chef-FS absolute path.  Again attempt to be nice but warn the user.
+          ui.warn("An absolute file system path that isn't a server path was provided: #{arg}")
+          ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+          inferred_path = @chef_fs_config.server_path(arg)
+          ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+        elsif @chef_fs_config.base_path.nil?
+          # These are all relative paths.  We can't resolve and root paths unless we are in the
+          # chef repo.
+          ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path.")
+          ui.error("Current working directory is '#{@chef_fs_config.cwd}'.")
           exit(1)
+        else
+          inferred_path = Chef::ChefFS::PathUtils::join(@chef_fs_config.base_path, arg)
         end
-        Chef::ChefFS::FilePattern.relative_to(@chef_fs_config.base_path, arg)
+        Chef::ChefFS::FilePattern.new(inferred_path)
       end
 
       def format_path(entry)
diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb
index 9ef75ce..595f966 100644
--- a/lib/chef/chef_fs/path_utils.rb
+++ b/lib/chef/chef_fs/path_utils.rb
@@ -23,31 +23,31 @@ class Chef
   module ChefFS
     class PathUtils
 
-      # If you are in 'source', this is what you would have to type to reach 'dest'
-      # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e'
-      # relative_to('/a/b', '/a/b') == '.'
-      def self.relative_to(dest, source)
-        # Skip past the common parts
-        source_parts = Chef::ChefFS::PathUtils.split(source)
-        dest_parts = Chef::ChefFS::PathUtils.split(dest)
-        i = 0
-        until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i]
-          i+=1
-        end
-        # dot-dot up from 'source' to the common ancestor, then
-        # descend to 'dest' from the common ancestor
-        result = Chef::ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i]))
-        result == '' ? '.' : result
-      end
+      # A Chef-FS path is a path in a chef-repository that can be used to address
+      # both files on a local file-system as well as objects on a chef server.
+      # These paths are stricter than file-system paths allowed on various OSes.
+      # Absolute Chef-FS paths begin with "/" (on windows, "\" is acceptable as well).
+      # "/" is used as the path element separator (on windows, "\" is acceptable as well).
+      # No directory/path element may contain a literal "\" character.  Any such characters
+      # encountered are either dealt with as separators (on windows) or as escape
+      # characters (on POSIX systems).  Relative Chef-FS paths may use ".." or "." but
+      # may never use these to back-out of the root of a Chef-FS path.  Any such extraneous
+      # ".."s are ignored.
+      # Chef-FS paths are case sensitive (since the paths on the server are).
+      # On OSes with case insensitive paths, you may be unable to locally deal with two
+      # objects whose server paths only differ by case.  OTOH, the case of path segments
+      # that are outside the Chef-FS root (such as when looking at a file-system absolute
+      # path to discover the Chef-FS root path) are handled in accordance to the rules
+      # of the local file-system and OS.
 
       def self.join(*parts)
         return "" if parts.length == 0
         # Determine if it started with a slash
         absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0] =~ /^#{regexp_path_separator}/
         # Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away)
-        parts = parts.map { |part| part.gsub(/^\/|\/$/, "") }
+        parts = parts.map { |part| part.gsub(/^#{regexp_path_separator}+|#{regexp_path_separator}+$/, '') }
         # Don't join empty bits
-        result = parts.select { |part| part != "" }.join("/")
+        result = parts.select { |part| part != '' }.join('/')
         # Put the / back on
         absolute ? "/#{result}" : result
       end
@@ -60,36 +60,67 @@ class Chef
         Chef::ChefFS::windows? ? '[\/\\\\]' : '/'
       end
 
+      # Given a server path, determines if it is absolute.
+      def self.is_absolute?(path)
+        !!(path =~ /^#{regexp_path_separator}/)
+      end
       # Given a path which may only be partly real (i.e. /x/y/z when only /x exists,
       # or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest
-      # part that actually exists.
+      # part that actually exists.  The paths operated on here are not Chef-FS paths.
+      # These are OS paths that may contain symlinks but may not also fully exist.
       #
       # If /x is a symlink to /blarghle, and has no subdirectories, then:
       # PathUtils.realest_path('/x/y/z') == '/blarghle/y/z'
       # PathUtils.realest_path('/x/*/z') == '/blarghle/*/z'
       # PathUtils.realest_path('/*/y/z') == '/*/y/z'
-      def self.realest_path(path)
-        path = Pathname.new(path)
-        begin
-          path.realpath.to_s
-        rescue Errno::ENOENT
-          dirname = path.dirname
-          if dirname
-            PathUtils.join(realest_path(dirname), path.basename.to_s)
-          else
-            path.to_s
+      #
+      # TODO: Move this to wherever util/path_helper is these days.
+      def self.realest_path(path, cwd = Dir.pwd)
+        path = File.expand_path(path, cwd)
+        parent_path = File.dirname(path)
+        suffix = []
+
+        # File.dirname happens to return the path as its own dirname if you're
+        # at the root (such as at \\foo\bar, C:\ or /)
+        until parent_path == path do
+          # This can occur if a path such as "C:" is given.  Ruby gives the parent as "C:."
+          # for reasons only it knows.
+          raise ArgumentError "Invalid path segment #{path}" if parent_path.length > path.length
+          begin
+            path = File.realpath(path)
+            break
+          rescue Errno::ENOENT
+            suffix << File.basename(path)
+            path = parent_path
+            parent_path = File.dirname(path)
           end
         end
+        File.join(path, *suffix.reverse)
       end
 
-      def self.descendant_of?(path, ancestor)
-        path[0,ancestor.length] == ancestor &&
-          (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/)
+      # Compares two path fragments according to the case-sentitivity of the host platform.
+      def self.os_path_eq?(left, right)
+        Chef::ChefFS::windows? ? left.casecmp(right) == 0 : left == right
       end
 
-      def self.is_absolute?(path)
-        path =~ /^#{regexp_path_separator}/
+      # Given two general OS-dependent file paths, determines the relative path of the
+      # child with respect to the ancestor.  Both child and ancestor must exist and be
+      # fully resolved - this is strictly a lexical comparison.  No trailing slashes
+      # and other shenanigans are allowed.
+      #
+      # TODO: Move this to util/path_helper.
+      def self.descendant_path(path, ancestor)
+        candidate_fragment = path[0, ancestor.length]
+        return nil unless PathUtils.os_path_eq?(candidate_fragment, ancestor)
+        if ancestor.length == path.length
+          ''
+        elsif path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/
+          path[ancestor.length+1..-1]
+        else
+          nil
+        end
       end
+
     end
   end
 end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index d04a3db..b2a00a7 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -3,7 +3,7 @@
 # Author:: Christopher Walters (<cw at opscode.com>)
 # Author:: Christopher Brown (<cb at opscode.com>)
 # Author:: Tim Hinderliter (<tim at opscode.com>)
-# Copyright:: Copyright (c) 2008-2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -50,6 +50,7 @@ require 'chef/run_lock'
 require 'chef/policy_builder'
 require 'chef/request_id'
 require 'chef/platform/rebooter'
+require 'chef/mixin/deprecation'
 require 'ohai'
 require 'rbconfig'
 
@@ -60,121 +61,283 @@ class Chef
   class Client
     include Chef::Mixin::PathSanity
 
-    # IO stream that will be used as 'STDOUT' for formatters. Formatters are
-    # configured during `initialize`, so this provides a convenience for
-    # setting alternative IO stream during tests.
-    STDOUT_FD = STDOUT
-
-    # IO stream that will be used as 'STDERR' for formatters. Formatters are
-    # configured during `initialize`, so this provides a convenience for
-    # setting alternative IO stream during tests.
-    STDERR_FD = STDERR
+    extend Chef::Mixin::Deprecation
 
-    # Clears all notifications for client run status events.
-    # Primarily for testing purposes.
-    def self.clear_notifications
-      @run_start_notifications = nil
-      @run_completed_successfully_notifications = nil
-      @run_failed_notifications = nil
-    end
-
-    # The list of notifications to be run when the client run starts.
-    def self.run_start_notifications
-      @run_start_notifications ||= []
-    end
-
-    # The list of notifications to be run when the client run completes
-    # successfully.
-    def self.run_completed_successfully_notifications
-      @run_completed_successfully_notifications ||= []
-    end
-
-    # The list of notifications to be run when the client run fails.
-    def self.run_failed_notifications
-      @run_failed_notifications ||= []
-    end
-
-    # Add a notification for the 'client run started' event. The notification
-    # is provided as a block. The current Chef::RunStatus object will be passed
-    # to the notification_block when the event is triggered.
-    def self.when_run_starts(&notification_block)
-      run_start_notifications << notification_block
-    end
-
-    # Add a notification for the 'client run success' event. The notification
-    # is provided as a block. The current Chef::RunStatus object will be passed
-    # to the notification_block when the event is triggered.
-    def self.when_run_completes_successfully(&notification_block)
-      run_completed_successfully_notifications << notification_block
-    end
+    #
+    # The status of the Chef run.
+    #
+    # @return [Chef::RunStatus]
+    #
+    attr_reader :run_status
 
-    # Add a notification for the 'client run failed' event. The notification
-    # is provided as a block. The current Chef::RunStatus is passed to the
-    # notification_block when the event is triggered.
-    def self.when_run_fails(&notification_block)
-      run_failed_notifications << notification_block
+    #
+    # The node represented by this client.
+    #
+    # @return [Chef::Node]
+    #
+    def node
+      run_status.node
     end
-
-    # Callback to fire notifications that the Chef run is starting
-    def run_started
-      self.class.run_start_notifications.each do |notification|
-        notification.call(run_status)
-      end
-      @events.run_started(run_status)
+    def node=(value)
+      run_status.node = value
     end
 
-    # Callback to fire notifications that the run completed successfully
-    def run_completed_successfully
-      success_handlers = self.class.run_completed_successfully_notifications
-      success_handlers.each do |notification|
-        notification.call(run_status)
-      end
-    end
+    #
+    # The ohai system used by this client.
+    #
+    # @return [Ohai::System]
+    #
+    attr_reader :ohai
 
-    # Callback to fire notifications that the Chef run failed
-    def run_failed
-      failure_handlers = self.class.run_failed_notifications
-      failure_handlers.each do |notification|
-        notification.call(run_status)
-      end
-    end
+    #
+    # The rest object used to communicate with the Chef server.
+    #
+    # @return [Chef::REST]
+    #
+    attr_reader :rest
 
-    attr_accessor :node
-    attr_accessor :ohai
-    attr_accessor :rest
+    #
+    # The runner used to converge.
+    #
+    # @return [Chef::Runner]
+    #
     attr_accessor :runner
 
+    #
+    # Extra node attributes that were applied to the node.
+    #
+    # @return [Hash]
+    #
     attr_reader :json_attribs
-    attr_reader :run_status
+
+    #
+    # The event dispatcher for the Chef run, including any configured output
+    # formatters and event loggers.
+    #
+    # @return [EventDispatch::Dispatcher]
+    #
+    # @see Chef::Formatters
+    # @see Chef::Config#formatters
+    # @see Chef::Config#stdout
+    # @see Chef::Config#stderr
+    # @see Chef::Config#force_logger
+    # @see Chef::Config#force_formatter
+    # TODO add stdout, stderr, and default formatters to Chef::Config so the
+    # defaults aren't calculated here.  Remove force_logger and force_formatter
+    # from this code.
+    # @see Chef::EventLoggers
+    # @see Chef::Config#disable_event_logger
+    # @see Chef::Config#event_loggers
+    # @see Chef::Config#event_handlers
+    #
     attr_reader :events
 
+    #
     # Creates a new Chef::Client.
+    #
+    # @param json_attribs [Hash] Node attributes to layer into the node when it is
+    #   fetched.
+    # @param args [Hash] Options:
+    # @option args [Array<RunList::RunListItem>] :override_runlist A runlist to
+    #   use instead of the node's embedded run list.
+    # @option args [Array<String>] :specific_recipes A list of recipe file paths
+    #   to load after the run list has been loaded.
+    #
     def initialize(json_attribs=nil, args={})
       @json_attribs = json_attribs || {}
-      @node = nil
-      @run_status = nil
-      @runner = nil
       @ohai = Ohai::System.new
 
       event_handlers = configure_formatters + configure_event_loggers
       event_handlers += Array(Chef::Config[:event_handlers])
 
       @events = EventDispatch::Dispatcher.new(*event_handlers)
+      # TODO it seems like a bad idea to be deletin' other peoples' hashes.
       @override_runlist = args.delete(:override_runlist)
       @specific_recipes = args.delete(:specific_recipes)
+      @run_status = Chef::RunStatus.new(nil, events)
 
       if new_runlist = args.delete(:runlist)
         @json_attribs["run_list"] = new_runlist
       end
+    end
+
+    #
+    # Do a full run for this Chef::Client.
+    #
+    # Locks the run while doing its job.
+    #
+    # Fires run_start before doing anything and fires run_completed or
+    # run_failed when finished.  Also notifies client listeners of run_started
+    # at the beginning of Compile, and run_completed_successfully or run_failed
+    # when all is complete.
+    #
+    # Phase 1: Setup
+    # --------------
+    # Gets information about the system and the run we are doing.
+    #
+    # 1. Run ohai to collect system information.
+    # 2. Register / connect to the Chef server (unless in solo mode).
+    # 3. Retrieve the node (or create a new one).
+    # 4. Merge in json_attribs, Chef::Config.environment, and override_run_list.
+    #
+    # @see #run_ohai
+    # @see #load_node
+    # @see #build_node
+    # @see Chef::Config#lockfile
+    # @see Chef::RunLock#acquire
+    #
+    # Phase 2: Compile
+    # ----------------
+    # Decides *what* we plan to converge by compiling recipes.
+    #
+    # 1. Sync required cookbooks to the local cache.
+    # 2. Load libraries from all cookbooks.
+    # 3. Load attributes from all cookbooks.
+    # 4. Load LWRPs from all cookbooks.
+    # 5. Load resource definitions from all cookbooks.
+    # 6. Load recipes in the run list.
+    # 7. Load recipes from the command line.
+    #
+    # @see #setup_run_context Syncs and compiles cookbooks.
+    # @see Chef::CookbookCompiler#compile
+    #
+    # Phase 3: Converge
+    # -----------------
+    # Brings the system up to date.
+    #
+    # 1. Converge the resources built from recipes in Phase 2.
+    # 2. Save the node.
+    # 3. Reboot if we were asked to.
+    #
+    # @see #converge_and_save
+    # @see Chef::Runner
+    #
+    # Phase 4: Audit
+    # --------------
+    # Runs 'control_group' audits in recipes.  This entire section can be enabled or disabled with config.
+    #
+    # 1. 'control_group' DSL collects audits during Phase 2
+    # 2. Audits are run using RSpec
+    # 3. Errors are collected and reported using the formatters
+    #
+    # @see #run_audits
+    # @see Chef::Audit::Runner#run
+    #
+    # @raise [Chef::Exceptions::RunFailedWrappingError] If converge or audit failed.
+    #
+    # @see Chef::Config#enforce_path_sanity
+    # @see Chef::Config#solo
+    # @see Chef::Config#audit_mode
+    #
+    # @return Always returns true.
+    #
+    def run
+      start_profiling
+
+      run_error = nil
+
+      runlock = RunLock.new(Chef::Config.lockfile)
+      # TODO feels like acquire should have its own block arg for this
+      runlock.acquire
+      # don't add code that may fail before entering this section to be sure to release lock
+      begin
+        runlock.save_pid
+
+        request_id = Chef::RequestID.instance.request_id
+        run_context = nil
+        events.run_start(Chef::VERSION)
+        Chef::Log.info("*** Chef #{Chef::VERSION} ***")
+        Chef::Log.info "Chef-client pid: #{Process.pid}"
+        Chef::Log.debug("Chef-client request_id: #{request_id}")
+        enforce_path_sanity
+        run_ohai
+
+        register unless Chef::Config[:solo]
+
+        load_node
+
+        build_node
+
+        run_status.run_id = request_id
+        run_status.start_clock
+        Chef::Log.info("Starting Chef Run for #{node.name}")
+        run_started
+
+        do_windows_admin_check
+
+        run_context = setup_run_context
+
+        if Chef::Config[:audit_mode] != :audit_only
+          converge_error = converge_and_save(run_context)
+        end
+
+        if Chef::Config[:why_run] == true
+          # why_run should probably be renamed to why_converge
+          Chef::Log.debug("Not running controls in 'why-run' mode - this mode is used to see potential converge changes")
+        elsif Chef::Config[:audit_mode] != :disabled
+          audit_error = run_audits(run_context)
+        end
+
+        # Raise converge_error so run_failed reporters/events are processed.
+        raise converge_error if converge_error
+
+        run_status.stop_clock
+        Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
+        run_completed_successfully
+        events.run_completed(node)
+
+        # keep this inside the main loop to get exception backtraces
+        end_profiling
+
+        # rebooting has to be the last thing we do, no exceptions.
+        Chef::Platform::Rebooter.reboot_if_needed!(node)
+      rescue Exception => run_error
+        # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
+        Chef::Log.debug("Re-raising exception: #{run_error.class} - #{run_error.message}\n#{run_error.backtrace.join("\n  ")}")
+        # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
+        if run_status
+          run_status.stop_clock
+          run_status.exception = run_error
+          run_failed
+        end
+        events.run_failed(run_error)
+      ensure
+        Chef::RequestID.instance.reset_request_id
+        request_id = nil
+        @run_status = nil
+        run_context = nil
+        runlock.release
+      end
 
-      # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
-      require 'chef/platform/provider_priority_map' unless defined? Chef::Platform::ProviderPriorityMap
-      require 'chef/platform/resource_priority_map' unless defined? Chef::Platform::ResourcePriorityMap
+      # Raise audit, converge, and other errors here so that we exit
+      # with the proper exit status code and everything gets raised
+      # as a RunFailedWrappingError
+      if run_error || converge_error || audit_error
+        error = if Chef::Config[:audit_mode] == :disabled
+                  run_error || converge_error
+                else
+                  e = if run_error == converge_error
+                    Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
+                  else
+                    Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
+                  end
+                  e.fill_backtrace
+                  e
+                end
+
+        Chef::Application.debug_stacktrace(error)
+        raise error
+      end
 
-      Chef.set_provider_priority_map(Chef::Platform::ProviderPriorityMap.instance)
-      Chef.set_resource_priority_map(Chef::Platform::ResourcePriorityMap.instance)
+      true
     end
 
+    #
+    # Private API
+    # TODO make this stuff protected or private
+    #
+
+    # @api private
     def configure_formatters
       formatters_for_run.map do |formatter_name, output_path|
         if output_path.nil?
@@ -187,6 +350,7 @@ class Chef
       end
     end
 
+    # @api private
     def formatters_for_run
       if Chef::Config.formatters.empty?
         [default_formatter]
@@ -195,6 +359,7 @@ class Chef
       end
     end
 
+    # @api private
     def default_formatter
       if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter]
         [:doc]
@@ -203,6 +368,7 @@ class Chef
       end
     end
 
+    # @api private
     def configure_event_loggers
       if Chef::Config.disable_event_logger
         []
@@ -219,8 +385,9 @@ class Chef
       end
     end
 
-    # Resource repoters send event information back to the chef server for processing.
-    # Can only be called after we have a @rest object
+    # Resource reporters send event information back to the chef server for
+    # processing.  Can only be called after we have a @rest object
+    # @api private
     def register_reporters
       [
         Chef::ResourceReporter.new(rest),
@@ -230,43 +397,123 @@ class Chef
       end
     end
 
+    #
+    # Callback to fire notifications that the Chef run is starting
+    #
+    # @api private
+    #
+    def run_started
+      self.class.run_start_notifications.each do |notification|
+        notification.call(run_status)
+      end
+      events.run_started(run_status)
+    end
+
+    #
+    # Callback to fire notifications that the run completed successfully
+    #
+    # @api private
+    #
+    def run_completed_successfully
+      success_handlers = self.class.run_completed_successfully_notifications
+      success_handlers.each do |notification|
+        notification.call(run_status)
+      end
+    end
+
+    #
+    # Callback to fire notifications that the Chef run failed
+    #
+    # @api private
+    #
+    def run_failed
+      failure_handlers = self.class.run_failed_notifications
+      failure_handlers.each do |notification|
+        notification.call(run_status)
+      end
+    end
+
+    #
     # Instantiates a Chef::Node object, possibly loading the node's prior state
-    # when using chef-client. Delegates to policy_builder.  Injects the built node
-    # into the Chef class.
+    # when using chef-client. Sets Chef.node to the new node.
     #
     # @return [Chef::Node] The node object for this Chef run
+    #
+    # @see Chef::PolicyBuilder#load_node
+    #
+    # @api private
+    #
     def load_node
       policy_builder.load_node
-      @node = policy_builder.node
-      Chef.set_node(@node)
+      run_status.node = policy_builder.node
+      Chef.set_node(policy_builder.node)
       node
     end
 
-    # Mutates the `node` object to prepare it for the chef run. Delegates to
-    # policy_builder
+    #
+    # Mutates the `node` object to prepare it for the chef run.
     #
     # @return [Chef::Node] The updated node object
+    #
+    # @see Chef::PolicyBuilder#build_node
+    #
+    # @api private
+    #
     def build_node
       policy_builder.build_node
-      @run_status = Chef::RunStatus.new(node, events)
+      run_status.node = node
       node
     end
 
+    #
+    # Sync cookbooks to local cache.
+    #
+    # TODO this appears to be unused.
+    #
+    # @see Chef::PolicyBuilder#sync_cookbooks
+    #
+    # @api private
+    #
+    def sync_cookbooks
+      policy_builder.sync_cookbooks
+    end
+
+    #
+    # Sets up the run context.
+    #
+    # @see Chef::PolicyBuilder#setup_run_context
+    #
+    # @return The newly set up run context
+    #
+    # @api private
     def setup_run_context
-      run_context = policy_builder.setup_run_context(@specific_recipes)
+      run_context = policy_builder.setup_run_context(specific_recipes)
       assert_cookbook_path_not_empty(run_context)
       run_status.run_context = run_context
       run_context
     end
 
-    def sync_cookbooks
-      policy_builder.sync_cookbooks
-    end
-
+    #
+    # The PolicyBuilder strategy for figuring out run list and cookbooks.
+    #
+    # @return [Chef::PolicyBuilder::Policyfile, Chef::PolicyBuilder::ExpandNodeObject]
+    #
+    # @api private
+    #
     def policy_builder
-      @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, @override_runlist, events)
+      @policy_builder ||= Chef::PolicyBuilder::Dynamic.new(node_name, ohai.data, json_attribs, override_runlist, events)
     end
 
+    #
+    # Save the updated node to Chef.
+    #
+    # Does not save if we are in solo mode or using override_runlist.
+    #
+    # @see Chef::Node#save
+    # @see Chef::Config#solo
+    #
+    # @api private
+    #
     def save_updated_node
       if Chef::Config[:solo]
         # nothing to do
@@ -274,16 +521,46 @@ class Chef
         Chef::Log.warn("Skipping final node save because override_runlist was given")
       else
         Chef::Log.debug("Saving the current state of node #{node_name}")
-        @node.save
+        node.save
       end
     end
 
+    #
+    # Run ohai plugins.  Runs all ohai plugins unless minimal_ohai is specified.
+    #
+    # Sends the ohai_completed event when finished.
+    #
+    # @see Chef::EventDispatcher#
+    # @see Chef::Config#minimal_ohai
+    #
+    # @api private
+    #
     def run_ohai
       filter = Chef::Config[:minimal_ohai] ? %w[fqdn machinename hostname platform platform_version os os_version] : nil
       ohai.all_plugins(filter)
-      @events.ohai_completed(node)
+      events.ohai_completed(node)
     end
 
+    #
+    # Figure out the node name we are working with.
+    #
+    # It tries these, in order:
+    # - Chef::Config.node_name
+    # - ohai[:fqdn]
+    # - ohai[:machinename]
+    # - ohai[:hostname]
+    #
+    # If we are running against a server with authentication protocol < 1.0, we
+    # *require* authentication protocol version 1.1.
+    #
+    # @raise [Chef::Exceptions::CannotDetermineNodeName] If the node name is not
+    #   set and cannot be determined via ohai.
+    #
+    # @see Chef::Config#node_name
+    # @see Chef::Config#authentication_protocol_version
+    #
+    # @api private
+    #
     def node_name
       name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:machinename] || ohai[:hostname]
       Chef::Config[:node_name] = name
@@ -292,6 +569,8 @@ class Chef
 
       # node names > 90 bytes only work with authentication protocol >= 1.1
       # see discussion in config.rb.
+      # TODO use a computed default in Chef::Config to determine this instead of
+      # setting it.
       if name.bytesize > 90
         Chef::Config[:authentication_protocol_version] = "1.1"
       end
@@ -300,46 +579,86 @@ class Chef
     end
 
     #
-    # === Returns
-    # rest<Chef::REST>:: returns Chef::REST connection object
+    # Determine our private key and set up the connection to the Chef server.
+    #
+    # Skips registration and fires the `skipping_registration` event if
+    # Chef::Config.client_key is unspecified or already exists.
+    #
+    # If Chef::Config.client_key does not exist, we register the client with the
+    # Chef server and fire the registration_start and registration_completed events.
+    #
+    # @return [Chef::REST] The server connection object.
+    #
+    # @see Chef::Config#chef_server_url
+    # @see Chef::Config#client_key
+    # @see Chef::ApiClient::Registration#run
+    # @see Chef::EventDispatcher#skipping_registration
+    # @see Chef::EventDispatcher#registration_start
+    # @see Chef::EventDispatcher#registration_completed
+    # @see Chef::EventDispatcher#registration_failed
+    #
+    # @api private
+    #
     def register(client_name=node_name, config=Chef::Config)
       if !config[:client_key]
-        @events.skipping_registration(client_name, config)
+        events.skipping_registration(client_name, config)
         Chef::Log.debug("Client key is unspecified - skipping registration")
       elsif File.exists?(config[:client_key])
-        @events.skipping_registration(client_name, config)
+        events.skipping_registration(client_name, config)
         Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration")
       else
-        @events.registration_start(node_name, config)
+        events.registration_start(node_name, config)
         Chef::Log.info("Client key #{config[:client_key]} is not present - registering")
         Chef::ApiClient::Registration.new(node_name, config[:client_key]).run
-        @events.registration_completed
+        events.registration_completed
       end
       # We now have the client key, and should use it from now on.
       @rest = Chef::REST.new(config[:chef_server_url], client_name, config[:client_key])
       register_reporters
     rescue Exception => e
+      # TODO this should probably only ever fire if we *started* registration.
+      # Move it to the block above.
       # TODO: munge exception so a semantic failure message can be given to the
       # user
-      @events.registration_failed(client_name, e, config)
+      events.registration_failed(client_name, e, config)
       raise
     end
 
-    # Converges the node.
     #
-    # === Returns
-    # The thrown exception, if there was one.  If this returns nil the converge was successful.
+    # Converges all compiled resources.
+    #
+    # Fires the converge_start, converge_complete and converge_failed events.
+    #
+    # If the exception `:end_client_run_early` is thrown during convergence, it
+    # does not mark the run complete *or* failed, and returns `nil`
+    #
+    # @param run_context The run context.
+    #
+    # @return The thrown exception, if we are in audit mode. `nil` means the
+    #   converge was successful or ended early.
+    #
+    # @raise Any converge exception, unless we are in audit mode, in which case
+    #   we *return* the exception.
+    #
+    # @see Chef::Runner#converge
+    # @see Chef::Config#audit_mode
+    # @see Chef::EventDispatch#converge_start
+    # @see Chef::EventDispatch#converge_complete
+    # @see Chef::EventDispatch#converge_failed
+    #
+    # @api private
+    #
     def converge(run_context)
       converge_exception = nil
       catch(:end_client_run_early) do
         begin
-          @events.converge_start(run_context)
+          events.converge_start(run_context)
           Chef::Log.debug("Converging node #{node_name}")
           @runner = Chef::Runner.new(run_context)
-          runner.converge
-          @events.converge_complete
+          @runner.converge
+          events.converge_complete
         rescue Exception => e
-          @events.converge_failed(e)
+          events.converge_failed(e)
           raise e if Chef::Config[:audit_mode] == :disabled
           converge_exception = e
         end
@@ -347,8 +666,28 @@ class Chef
       converge_exception
     end
 
+    #
+    # Converge the node via and then save it if successful.
+    #
+    # @param run_context The run context.
+    #
+    # @return The thrown exception, if we are in audit mode. `nil` means the
+    #   converge was successful or ended early.
+    #
+    # @raise Any converge or node save exception, unless we are in audit mode,
+    #   in which case we *return* the exception.
+    #
+    # @see #converge
+    # @see #save_updated_mode
+    # @see Chef::Config#audit_mode
+    #
+    # @api private
+    #
     # We don't want to change the old API on the `converge` method to have it perform
     # saving.  So we wrap it in this method.
+    # TODO given this seems to be pretty internal stuff, how badly do we need to
+    # split this stuff up?
+    #
     def converge_and_save(run_context)
       converge_exception = converge(run_context)
       unless converge_exception
@@ -362,37 +701,67 @@ class Chef
       converge_exception
     end
 
+    #
+    # Run the audit phase.
+    #
+    # Triggers the audit_phase_start, audit_phase_complete and
+    # audit_phase_failed events.
+    #
+    # @param run_context The run context.
+    #
+    # @return Any thrown exceptions. `nil` if successful.
+    #
+    # @see Chef::Audit::Runner#run
+    # @see Chef::EventDispatch#audit_phase_start
+    # @see Chef::EventDispatch#audit_phase_complete
+    # @see Chef::EventDispatch#audit_phase_failed
+    #
+    # @api private
+    #
     def run_audits(run_context)
-      audit_exception = nil
       begin
-        @events.audit_phase_start(run_status)
+        events.audit_phase_start(run_status)
         Chef::Log.info("Starting audit phase")
         auditor = Chef::Audit::Runner.new(run_context)
         auditor.run
         if auditor.failed?
-          raise Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+          audit_exception = Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+          @events.audit_phase_failed(audit_exception, Chef::Audit::Logger.read_buffer)
+        else
+          @events.audit_phase_complete(Chef::Audit::Logger.read_buffer)
         end
-        @events.audit_phase_complete
       rescue Exception => e
         Chef::Log.error("Audit phase failed with error message: #{e.message}")
-        @events.audit_phase_failed(e)
+        @events.audit_phase_failed(e, Chef::Audit::Logger.read_buffer)
         audit_exception = e
       end
       audit_exception
     end
 
-    # Expands the run list. Delegates to the policy_builder.
     #
-    # Normally this does not need to be called from here, it will be called by
-    # build_node. This is provided so external users (like the chefspec
-    # project) can inject custom behavior into the run process.
+    # Expands the run list.
+    #
+    # @return [Chef::RunListExpansion] The expanded run list.
+    #
+    # @see Chef::PolicyBuilder#expand_run_list
     #
-    # === Returns
-    # RunListExpansion: A RunListExpansion or API compatible object.
     def expanded_run_list
       policy_builder.expand_run_list
     end
 
+    #
+    # Check if the user has Administrator privileges on windows.
+    #
+    # Throws an error if the user is not an admin, and
+    # `Chef::Config.fatal_windows_admin_check` is true.
+    #
+    # @raise [Chef::Exceptions::WindowsNotAdmin] If the user is not an admin.
+    #
+    # @see Chef::platform#windows?
+    # @see Chef::Config#fatal_windows_admin_check
+    #
+    # @api private
+    #
     def do_windows_admin_check
       if Chef::Platform.windows?
         Chef::Log.debug("Checking for administrator privileges....")
@@ -412,98 +781,142 @@ class Chef
       end
     end
 
-    # Do a full run for this Chef::Client.  Calls:
-    #
-    #  * run_ohai - Collect information about the system
-    #  * build_node - Get the last known state, merge with local changes
-    #  * register - If not in solo mode, make sure the server knows about this client
-    #  * sync_cookbooks - If not in solo mode, populate the local cache with the node's cookbooks
-    #  * converge - Bring this system up to date
-    #
-    # === Returns
-    # true:: Always returns true.
-    def run
-      runlock = RunLock.new(Chef::Config.lockfile)
-      runlock.acquire
-      # don't add code that may fail before entering this section to be sure to release lock
-      begin
-        runlock.save_pid
+    # Notification registration
+    class<<self
+      #
+      # Add a listener for the 'client run started' event.
+      #
+      # @param notification_block The callback (takes |run_status| parameter).
+      # @yieldparam [Chef::RunStatus] run_status The run status.
+      #
+      def when_run_starts(&notification_block)
+        run_start_notifications << notification_block
+      end
 
-        request_id = Chef::RequestID.instance.request_id
-        run_context = nil
-        @events.run_start(Chef::VERSION)
-        Chef::Log.info("*** Chef #{Chef::VERSION} ***")
-        Chef::Log.info "Chef-client pid: #{Process.pid}"
-        Chef::Log.debug("Chef-client request_id: #{request_id}")
-        enforce_path_sanity
-        run_ohai
+      #
+      # Add a listener for the 'client run success' event.
+      #
+      # @param notification_block The callback (takes |run_status| parameter).
+      # @yieldparam [Chef::RunStatus] run_status The run status.
+      #
+      def when_run_completes_successfully(&notification_block)
+        run_completed_successfully_notifications << notification_block
+      end
 
-        register unless Chef::Config[:solo]
+      #
+      # Add a listener for the 'client run failed' event.
+      #
+      # @param notification_block The callback (takes |run_status| parameter).
+      # @yieldparam [Chef::RunStatus] run_status The run status.
+      #
+      def when_run_fails(&notification_block)
+        run_failed_notifications << notification_block
+      end
 
-        load_node
+      #
+      # Clears all listeners for client run status events.
+      #
+      # Primarily for testing purposes.
+      #
+      # @api private
+      #
+      def clear_notifications
+        @run_start_notifications = nil
+        @run_completed_successfully_notifications = nil
+        @run_failed_notifications = nil
+      end
 
-        build_node
+      #
+      # TODO These seem protected to me.
+      #
+
+      #
+      # Listeners to be run when the client run starts.
+      #
+      # @return [Array<Proc>]
+      #
+      # @api private
+      #
+      def run_start_notifications
+        @run_start_notifications ||= []
+      end
 
-        run_status.run_id = request_id
-        run_status.start_clock
-        Chef::Log.info("Starting Chef Run for #{node.name}")
-        run_started
+      #
+      # Listeners to be run when the client run completes successfully.
+      #
+      # @return [Array<Proc>]
+      #
+      # @api private
+      #
+      def run_completed_successfully_notifications
+        @run_completed_successfully_notifications ||= []
+      end
 
-        do_windows_admin_check
+      #
+      # Listeners to be run when the client run fails.
+      #
+      # @return [Array<Proc>]
+      #
+      # @api private
+      #
+      def run_failed_notifications
+        @run_failed_notifications ||= []
+      end
+    end
 
-        run_context = setup_run_context
+    #
+    # IO stream that will be used as 'STDOUT' for formatters. Formatters are
+    # configured during `initialize`, so this provides a convenience for
+    # setting alternative IO stream during tests.
+    #
+    # @api private
+    #
+    STDOUT_FD = STDOUT
 
-        if Chef::Config[:audit_mode] != :audit_only
-          converge_error = converge_and_save(run_context)
-        end
+    #
+    # IO stream that will be used as 'STDERR' for formatters. Formatters are
+    # configured during `initialize`, so this provides a convenience for
+    # setting alternative IO stream during tests.
+    #
+    # @api private
+    #
+    STDERR_FD = STDERR
 
-        if Chef::Config[:why_run] == true
-          # why_run should probably be renamed to why_converge
-          Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes")
-        elsif Chef::Config[:audit_mode] != :disabled
-          audit_error = run_audits(run_context)
-        end
+    #
+    # Deprecated writers
+    #
 
-        if converge_error || audit_error
-          e = Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
-          e.fill_backtrace
-          raise e
-        end
+    include Chef::Mixin::Deprecation
+    deprecated_attr_writer :ohai, "There is no alternative. Leave ohai alone!"
+    deprecated_attr_writer :rest, "There is no alternative. Leave rest alone!"
+    deprecated_attr :runner, "There is no alternative. Leave runner alone!"
 
-        run_status.stop_clock
-        Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
-        run_completed_successfully
-        @events.run_completed(node)
+    private
 
-        # rebooting has to be the last thing we do, no exceptions.
-        Chef::Platform::Rebooter.reboot_if_needed!(node)
+    attr_reader :override_runlist
+    attr_reader :specific_recipes
 
-        true
+    def profiling_prereqs!
+      require 'ruby-prof'
+    rescue LoadError
+      raise "You must have the ruby-prof gem installed in order to use --profile-ruby"
+    end
 
-      rescue Exception => e
-        # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
-        Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n  ")}")
-        # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
-        if run_status
-          run_status.stop_clock
-          run_status.exception = e
-          run_failed
-        end
-        Chef::Application.debug_stacktrace(e)
-        @events.run_failed(e)
-        raise
-      ensure
-        Chef::RequestID.instance.reset_request_id
-        request_id = nil
-        @run_status = nil
-        run_context = nil
-        runlock.release
-        GC.start
-      end
-      true
+    def start_profiling
+      return unless Chef::Config[:profile_ruby]
+      profiling_prereqs!
+      RubyProf.start
     end
 
-    private
+    def end_profiling
+      return unless Chef::Config[:profile_ruby]
+      profiling_prereqs!
+      path = Chef::FileCache.create_cache_path("graph_profile.out", false)
+      File.open(path, "w+") do |file|
+        RubyProf::GraphPrinter.new(RubyProf.stop).print(file, {})
+      end
+      Chef::Log.warn("Ruby execution profile dumped to #{path}")
+    end
 
     def empty_directory?(path)
       !File.exists?(path) || (Dir.entries(path).size <= 2)
@@ -536,7 +949,6 @@ class Chef
 
       Chef::ReservedNames::Win32::Security.has_admin_privileges?
     end
-
   end
 end
 
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index 25557b0..a43985f 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -20,727 +20,66 @@
 # limitations under the License.
 
 require 'chef/log'
-require 'chef/exceptions'
-require 'mixlib/config'
-require 'chef/util/selinux'
-require 'chef/util/path_helper'
-require 'pathname'
-require 'chef/mixin/shell_out'
+require 'chef-config/logger'
 
-class Chef
-  class Config
-
-    extend Mixlib::Config
-    extend Chef::Mixin::ShellOut
-
-    PathHelper = Chef::Util::PathHelper
-
-    # Evaluates the given string as config.
-    #
-    # +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file.
-    def self.from_string(string, filename)
-      self.instance_eval(string, filename, 1)
-    end
-
-    # Manages the chef secret session key
-    # === Returns
-    # <newkey>:: A new or retrieved session key
-    #
-    def self.manage_secret_key
-      newkey = nil
-      if Chef::FileCache.has_key?("chef_server_cookie_id")
-        newkey = Chef::FileCache.load("chef_server_cookie_id")
-      else
-        chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
-        newkey = ""
-        40.times { |i| newkey << chars[rand(chars.size-1)] }
-        Chef::FileCache.store("chef_server_cookie_id", newkey)
-      end
-      newkey
-    end
-
-    def self.inspect
-      configuration.inspect
-    end
-
-    def self.platform_specific_path(path)
-      path = PathHelper.cleanpath(path)
-      if Chef::Platform.windows?
-        # turns \etc\chef\client.rb and \var\chef\client.rb into C:/chef/client.rb
-        if env['SYSTEMDRIVE'] && path[0] == '\\' && path.split('\\')[2] == 'chef'
-          path = PathHelper.join(env['SYSTEMDRIVE'], path.split('\\', 3)[2])
-        end
-      end
-      path
-    end
-
-    def self.add_formatter(name, file_path=nil)
-      formatters << [name, file_path]
-    end
-
-    def self.add_event_logger(logger)
-      event_handlers << logger
-    end
-
-    # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.)
-    configurable(:config_file)
-
-    default(:config_dir) do
-      if config_file
-        PathHelper.dirname(config_file)
-      else
-        PathHelper.join(user_home, ".chef", "")
-      end
-    end
-
-    default :formatters, []
-
-    # Override the config dispatch to set the value of multiple server options simultaneously
-    #
-    # === Parameters
-    # url<String>:: String to be set for all of the chef-server-api URL's
-    #
-    configurable(:chef_server_url).writes_value { |url| url.to_s.strip }
-
-    # When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel.
-    # So while this is basically identical to what method_missing would do, we pull
-    # it up here and get a real method written so that things get dispatched
-    # properly.
-    configurable(:daemonize).writes_value { |v| v }
-
-    # The root where all local chef object data is stored.  cookbooks, data bags,
-    # environments are all assumed to be in separate directories under this.
-    # chef-solo uses these directories for input data.  knife commands
-    # that upload or download files (such as knife upload, knife role from file,
-    # etc.) work.
-    default :chef_repo_path do
-      if self.configuration[:cookbook_path]
-        if self.configuration[:cookbook_path].kind_of?(String)
-          File.expand_path('..', self.configuration[:cookbook_path])
-        else
-          self.configuration[:cookbook_path].map do |path|
-            File.expand_path('..', path)
-          end
-        end
-      else
-        cache_path
-      end
-    end
-
-    def self.find_chef_repo_path(cwd)
-      # In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it.
-      # This allows us to run config-free.
-      path = cwd
-      until File.directory?(PathHelper.join(path, "cookbooks"))
-        new_path = File.expand_path('..', path)
-        if new_path == path
-          Chef::Log.warn("No cookbooks directory found at or above current directory.  Assuming #{Dir.pwd}.")
-          return Dir.pwd
-        end
-        path = new_path
-      end
-      Chef::Log.info("Auto-discovered chef repository at #{path}")
-      path
-    end
-
-    def self.derive_path_from_chef_repo_path(child_path)
-      if chef_repo_path.kind_of?(String)
-        PathHelper.join(chef_repo_path, child_path)
-      else
-        chef_repo_path.map { |path| PathHelper.join(path, child_path)}
-      end
-    end
-
-    # Location of acls on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/acls.
-    # Only applies to Enterprise Chef commands.
-    default(:acl_path) { derive_path_from_chef_repo_path('acls') }
-
-    # Location of clients on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/acls.
-    default(:client_path) { derive_path_from_chef_repo_path('clients') }
-
-    # Location of cookbooks on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/cookbooks.  If chef_repo_path
-    # is not specified, this is set to [/var/chef/cookbooks, /var/chef/site-cookbooks]).
-    default(:cookbook_path) do
-      if self.configuration[:chef_repo_path]
-        derive_path_from_chef_repo_path('cookbooks')
-      else
-        Array(derive_path_from_chef_repo_path('cookbooks')).flatten +
-          Array(derive_path_from_chef_repo_path('site-cookbooks')).flatten
-      end
-    end
-
-    # Location of containers on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/containers.
-    # Only applies to Enterprise Chef commands.
-    default(:container_path) { derive_path_from_chef_repo_path('containers') }
-
-    # Location of data bags on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/data_bags.
-    default(:data_bag_path) { derive_path_from_chef_repo_path('data_bags') }
-
-    # Location of environments on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/environments.
-    default(:environment_path) { derive_path_from_chef_repo_path('environments') }
-
-    # Location of groups on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/groups.
-    # Only applies to Enterprise Chef commands.
-    default(:group_path) { derive_path_from_chef_repo_path('groups') }
-
-    # Location of nodes on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/nodes.
-    default(:node_path) { derive_path_from_chef_repo_path('nodes') }
-
-    # Location of roles on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/roles.
-    default(:role_path) { derive_path_from_chef_repo_path('roles') }
-
-    # Location of users on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/users.
-    # Does not apply to Enterprise Chef commands.
-    default(:user_path) { derive_path_from_chef_repo_path('users') }
-
-    # Location of policies on disk. String or array of strings.
-    # Defaults to <chef_repo_path>/policies.
-    default(:policy_path) { derive_path_from_chef_repo_path('policies') }
-
-    # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity
-    default :enforce_path_sanity, true
-
-    # Formatted Chef Client output is a beta feature, disabled by default:
-    default :formatter, "null"
-
-    # The number of times the client should retry when registering with the server
-    default :client_registration_retries, 5
-
-    # An array of paths to search for knife exec scripts if they aren't in the current directory
-    default :script_path, []
-
-    # The root of all caches (checksums, cache and backup).  If local mode is on,
-    # this is under the user's home directory.
-    default(:cache_path) do
-      if local_mode
-        PathHelper.join(config_dir, 'local-mode-cache')
-      else
-        primary_cache_root = platform_specific_path("/var")
-        primary_cache_path = platform_specific_path("/var/chef")
-        # Use /var/chef as the cache path only if that folder exists and we can read and write
-        # into it, or /var exists and we can read and write into it (we'll create /var/chef later).
-        # Otherwise, we'll create .chef under the user's home directory and use that as
-        # the cache path.
-        unless path_accessible?(primary_cache_path) || path_accessible?(primary_cache_root)
-          secondary_cache_path = PathHelper.join(user_home, '.chef')
-          Chef::Log.info("Unable to access cache at #{primary_cache_path}. Switching cache to #{secondary_cache_path}")
-          secondary_cache_path
-        else
-          primary_cache_path
-        end
-      end
-    end
-
-    # Returns true only if the path exists and is readable and writeable for the user.
-    def self.path_accessible?(path)
-      File.exists?(path) && File.readable?(path) && File.writable?(path)
-    end
-
-    # Where cookbook files are stored on the server (by content checksum)
-    default(:checksum_path) { PathHelper.join(cache_path, "checksums") }
-
-    # Where chef's cache files should be stored
-    default(:file_cache_path) { PathHelper.join(cache_path, "cache") }
-
-    # Where backups of chef-managed files should go
-    default(:file_backup_path) { PathHelper.join(cache_path, "backup") }
-
-    # The chef-client (or solo) lockfile.
-    #
-    # If your `file_cache_path` resides on a NFS (or non-flock()-supporting
-    # fs), it's recommended to set this to something like
-    # '/tmp/chef-client-running.pid'
-    default(:lockfile) { PathHelper.join(file_cache_path, "chef-client-running.pid") }
-
-    ## Daemonization Settings ##
-    # What user should Chef run as?
-    default :user, nil
-    default :group, nil
-    default :umask, 0022
-
-    # Valid log_levels are:
-    # * :debug
-    # * :info
-    # * :warn
-    # * :fatal
-    # These work as you'd expect. There is also a special `:auto` setting.
-    # When set to :auto, Chef will auto adjust the log verbosity based on
-    # context. When a tty is available (usually because the user is running chef
-    # in a console), the log level is set to :warn, and output formatters are
-    # used as the primary mode of output. When a tty is not available, the
-    # logger is the primary mode of output, and the log level is set to :info
-    default :log_level, :auto
-
-    # Logging location as either an IO stream or string representing log file path
-    default :log_location, STDOUT
-
-    # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty
-    default :force_formatter, false
-
-    # Using `force_logger` causes chef to default to logger output when STDOUT is a tty
-    default :force_logger, false
-
-    default :http_retry_count, 5
-    default :http_retry_delay, 5
-    default :interval, nil
-    default :once, nil
-    default :json_attribs, nil
-    # toggle info level log items that can create a lot of output
-    default :verbose_logging, true
-    default :node_name, nil
-    default :diff_disabled,           false
-    default :diff_filesize_threshold, 10000000
-    default :diff_output_threshold,   1000000
-    default :local_mode, false
-
-    default :pid_file, nil
-
-    # Whether Chef Zero local mode should bind to a port. All internal requests
-    # will go through the socketless code path regardless, so the socket is
-    # only needed if other processes will connect to the local mode server.
-    #
-    # For compatibility this is set to true but it will be changed to false in
-    # the future.
-    default :listen, true
+# DI our logger into ChefConfig before we load the config. Some defaults are
+# auto-detected, and this emits log messages on some systems, all of which will
+# occur at require-time. So we need to set the logger first.
+ChefConfig.logger = Chef::Log
 
-    config_context :chef_zero do
-      config_strict_mode true
-      default(:enabled) { Chef::Config.local_mode }
-      default :host, 'localhost'
-      default :port, 8889.upto(9999) # Will try ports from 8889-9999 until one works
-    end
-    default :chef_server_url,   "https://localhost:443"
-
-    default :rest_timeout, 300
-    default :yum_timeout, 900
-    default :yum_lock_timeout, 30
-    default :solo,  false
-    default :splay, nil
-    default :why_run, false
-    default :color, false
-    default :client_fork, true
-    default :ez, false
-    default :enable_reporting, true
-    default :enable_reporting_url_fatals, false
-    # Possible values for :audit_mode
-    # :enabled, :disabled, :audit_only,
-    #
-    # TODO: 11 Dec 2014: Currently audit-mode is an experimental feature
-    # and is disabled by default. When users choose to enable audit-mode,
-    # a warning is issued in application/client#reconfigure.
-    # This can be removed when audit-mode is enabled by default.
-    default :audit_mode, :disabled
-
-    # Chef only needs ohai to run the hostname plugin for the most basic
-    # functionality. If the rest of the ohai plugins are not needed (like in
-    # most of our testing scenarios)
-    default :minimal_ohai, false
-
-    # Policyfile is an experimental feature where a node gets its run list and
-    # cookbook version set from a single document on the server instead of
-    # expanding the run list and having the server compute the cookbook version
-    # set based on environment constraints.
-    #
-    # Because this feature is experimental, it is not recommended for
-    # production use. Developent/release of this feature may not adhere to
-    # semver guidelines.
-    default :use_policyfile, false
-
-    # Set these to enable SSL authentication / mutual-authentication
-    # with the server
-
-    # Client side SSL cert/key for mutual auth
-    default :ssl_client_cert, nil
-    default :ssl_client_key, nil
-
-    # Whether or not to verify the SSL cert for all HTTPS requests. When set to
-    # :verify_peer (default), all HTTPS requests will be validated regardless of other
-    # SSL verification settings. When set to :verify_none no HTTPS requests will
-    # be validated.
-    default :ssl_verify_mode, :verify_peer
-
-    # Whether or not to verify the SSL cert for HTTPS requests to the Chef
-    # server API. If set to `true`, the server's cert will be validated
-    # regardless of the :ssl_verify_mode setting. This is set to `true` when
-    # running in local-mode.
-    # NOTE: This is a workaround until verify_peer is enabled by default.
-    default(:verify_api_cert) { Chef::Config.local_mode }
+require 'chef-config/config'
+require 'chef/platform/query_helpers'
 
-    # Path to the default CA bundle files.
-    default :ssl_ca_path, nil
-    default(:ssl_ca_file) do
-      if Chef::Platform.windows? and embedded_path = embedded_dir
-        cacert_path = File.join(embedded_path, "ssl/certs/cacert.pem")
-        cacert_path if File.exist?(cacert_path)
-      else
-        nil
-      end
-    end
-
-    # A directory that contains additional SSL certificates to trust. Any
-    # certificates in this directory will be added to whatever CA bundle ruby
-    # is using. Use this to add self-signed certs for your Chef Server or local
-    # HTTP file servers.
-    default(:trusted_certs_dir) { PathHelper.join(config_dir, "trusted_certs") }
-
-    # Where should chef-solo download recipes from?
-    default :recipe_url, nil
-
-    # Sets the version of the signed header authentication protocol to use (see
-    # the 'mixlib-authorization' project for more detail). Currently, versions
-    # 1.0 and 1.1 are available; however, the chef-server must first be
-    # upgraded to support version 1.1 before clients can begin using it.
-    #
-    # Version 1.1 of the protocol is required when using a `node_name` greater
-    # than ~90 bytes (~90 ascii characters), so chef-client will automatically
-    # switch to using version 1.1 when `node_name` is too large for the 1.0
-    # protocol. If you intend to use large node names, ensure that your server
-    # supports version 1.1. Automatic detection of large node names means that
-    # users will generally not need to manually configure this.
-    #
-    # In the future, this configuration option may be replaced with an
-    # automatic negotiation scheme.
-    default :authentication_protocol_version, "1.0"
-
-    # This key will be used to sign requests to the Chef server. This location
-    # must be writable by Chef during initial setup when generating a client
-    # identity on the server.
-    #
-    # The chef-server will look up the public key for the client using the
-    # `node_name` of the client.
-    #
-    # If chef-zero is enabled, this defaults to nil (no authentication).
-    default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") }
-
-    # When registering the client, should we allow the client key location to
-    # be a symlink?  eg: /etc/chef/client.pem -> /etc/chef/prod-client.pem
-    # If the path of the key goes through a directory like /tmp this should
-    # never be set to true or its possibly an easily exploitable security hole.
-    default :follow_client_key_symlink, false
-
-    # This secret is used to decrypt encrypted data bag items.
-    default(:encrypted_data_bag_secret) do
-      if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret"))
-        platform_specific_path("/etc/chef/encrypted_data_bag_secret")
-      else
-        nil
-      end
-    end
-
-    # As of Chef 11.0, version "1" is the default encrypted data bag item
-    # format. Version "2" is available which adds encrypt-then-mac protection.
-    # To maintain compatibility, versions other than 1 must be opt-in.
-    #
-    # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure.
-    # Set this to `3` if you have chef-client 11.?.0+, ruby 2 and OpenSSL >= 1.0.1 in your infrastructure. (TODO)
-    default :data_bag_encrypt_version, 1
-
-    # When reading data bag items, any supported version is accepted. However,
-    # if all encrypted data bags have been generated with the version 2 format,
-    # it is recommended to disable support for earlier formats to improve
-    # security. For example, the version 2 format is identical to version 1
-    # except for the addition of an HMAC, so an attacker with MITM capability
-    # could downgrade an encrypted data bag to version 1 as part of an attack.
-    default :data_bag_decrypt_minimum_version, 0
-
-    # If there is no file in the location given by `client_key`, chef-client
-    # will temporarily use the "validator" identity to generate one. If the
-    # `client_key` is not present and the `validation_key` is also not present,
-    # chef-client will not be able to authenticate to the server.
-    #
-    # The `validation_key` is never used if the `client_key` exists.
-    #
-    # If chef-zero is enabled, this defaults to nil (no authentication).
-    default(:validation_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/validation.pem") }
-    default :validation_client_name, "chef-validator"
-
-    # When creating a new client via the validation_client account, Chef 11
-    # servers allow the client to generate a key pair locally and send the
-    # public key to the server. This is more secure and helps offload work from
-    # the server, enhancing scalability. If enabled and the remote server
-    # implements only the Chef 10 API, client registration will not work
-    # properly.
-    #
-    # The default value is `true`. Set to `false` to disable client-side key
-    # generation (server generates client keys).
-    default(:local_key_generation) { true }
+# Ohai::Config defines its own log_level and log_location. When loaded, it will
+# override the default ChefConfig::Config values. We save them here before
+# loading ohai/config so that we can override them again inside Chef::Config.
+#
+# REMOVEME once these configurables are removed from the top level of Ohai.
+LOG_LEVEL = ChefConfig::Config[:log_level] unless defined? LOG_LEVEL
+LOG_LOCATION = ChefConfig::Config[:log_location] unless defined? LOG_LOCATION
 
-    # Zypper package provider gpg checks. Set to true to enable package
-    # gpg signature checking. This will be default in the
-    # future. Setting to false disables the warnings.
-    # Leaving this set to nil or false is a security hazard!
-    default :zypper_check_gpg, nil
+# Load the ohai config into the chef config. We can't have an empty ohai
+# configuration context because `ohai.plugins_path << some_path` won't work,
+# and providing default ohai config values here isn't DRY.
+require 'ohai/config'
 
-    # Report Handlers
-    default :report_handlers, []
+class Chef
+  Config = ChefConfig::Config
 
-    # Event Handlers
-    default :event_handlers, []
+  # We re-open ChefConfig::Config to add additional settings. Generally,
+  # everything should go in chef-config so it's shared with whoever uses that.
+  # We make execeptions to that rule when:
+  # * The functionality isn't likely to be useful outside of Chef
+  # * The functionality makes use of a dependency we don't want to add to chef-config
+  class Config
 
-    default :disable_event_loggers, false
     default :event_loggers do
       evt_loggers = []
-      if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
+      if ChefConfig.windows? && !(Chef::Platform.windows_server_2003? ||
+          Chef::Platform.windows_nano_server?)
         evt_loggers << :win_evt
       end
       evt_loggers
     end
 
-    # Exception Handlers
-    default :exception_handlers, []
-
-    # Start handlers
-    default :start_handlers, []
-
-    # Syntax Check Cache. Knife keeps track of files that is has already syntax
-    # checked by storing files in this directory. `syntax_check_cache_path` is
-    # the new (and preferred) configuration setting. If not set, knife will
-    # fall back to using cache_options[:path], which is deprecated but exists in
-    # many client configs generated by pre-Chef-11 bootstrappers.
-    default(:syntax_check_cache_path) { cache_options[:path] }
-
-    # Deprecated:
-    # Move this to the default value of syntax_cache_path when this is removed.
-    default(:cache_options) { { :path => PathHelper.join(config_dir, "syntaxcache") } }
-
-    # Whether errors should be raised for deprecation warnings. When set to
-    # `false` (the default setting), a warning is emitted but code using
-    # deprecated methods/features/etc. should work normally otherwise. When set
-    # to `true`, usage of deprecated methods/features will raise a
-    # `DeprecatedFeatureError`. This is used by Chef's tests to ensure that
-    # deprecated functionality is not used internally by Chef.  End users
-    # should generally leave this at the default setting (especially in
-    # production), but it may be useful when testing cookbooks or other code if
-    # the user wishes to aggressively address deprecations.
-    default(:treat_deprecation_warnings_as_errors) do
-      # Using an environment variable allows this setting to be inherited in
-      # tests that spawn new processes.
-      ENV.key?("CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS")
-    end
-
-    # knife configuration data
-    config_context :knife do
-      default :ssh_port, nil
-      default :ssh_user, nil
-      default :ssh_attribute, nil
-      default :ssh_gateway, nil
-      default :bootstrap_version, nil
-      default :bootstrap_proxy, nil
-      default :bootstrap_template, nil
-      default :secret, nil
-      default :secret_file, nil
-      default :identity_file, nil
-      default :host_key_verify, nil
-      default :forward_agent, nil
-      default :sort_status_reverse, nil
-      default :hints, {}
-    end
-
-    def self.set_defaults_for_windows
-      # Those lists of regular expressions define what chef considers a
-      # valid user and group name
-      # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx
-      principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+'
-      default :user_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
-      default :group_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
-
-      default :fatal_windows_admin_check, false
-    end
-
-    def self.set_defaults_for_nix
-      # Those lists of regular expressions define what chef considers a
-      # valid user and group name
-      #
-      # user/group cannot start with '-', '+' or '~'
-      # user/group cannot contain ':', ',' or non-space-whitespace or null byte
-      # everything else is allowed (UTF-8, spaces, etc) and we delegate to your O/S useradd program to barf or not
-      # copies: http://anonscm.debian.org/viewvc/pkg-shadow/debian/trunk/debian/patches/506_relaxed_usernames?view=markup
-      default :user_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
-      default :group_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
-    end
-
-    # Those lists of regular expressions define what chef considers a
-    # valid user and group name
-    if Chef::Platform.windows?
-      set_defaults_for_windows
-    else
-      set_defaults_for_nix
-    end
-
-    # This provides a hook which rspec can stub so that we can avoid twiddling
-    # global state in tests.
-    def self.env
-      ENV
-    end
-
-    def self.windows_home_path
-      Chef::Log.deprecation("Chef::Config.windows_home_path is now deprecated.  Consider using Chef::Util::PathHelper.home instead.")
-      PathHelper.home
-    end
-
-    # returns a platform specific path to the user home dir if set, otherwise default to current directory.
-    default( :user_home ) { PathHelper.home || Dir.pwd }
-
-    # Enable file permission fixup for selinux. Fixup will be done
-    # only if selinux is enabled in the system.
-    default :enable_selinux_file_permission_fixup, true
-
-    # Use atomic updates (i.e. move operation) while updating contents
-    # of the files resources. When set to false copy operation is
-    # used to update files.
-    default :file_atomic_update, true
-
-    # There are 3 possible values for this configuration setting.
-    # true => file staging is done in the destination directory
-    # false => file staging is done via tempfiles under ENV['TMP']
-    # :auto => file staging will try using destination directory if possible and
-    #   will fall back to ENV['TMP'] if destination directory is not usable.
-    default :file_staging_uses_destdir, :auto
-
-    # Exit if another run is in progress and the chef-client is unable to
-    # get the lock before time expires. If nil, no timeout is enforced. (Exits
-    # immediately if 0.)
-    default :run_lock_timeout, nil
-
-    # Number of worker threads for syncing cookbooks in parallel. Increasing
-    # this number can result in gateway errors from the server (namely 503 and 504).
-    # If you are seeing this behavior while using the default setting, reducing
-    # the number of threads will help.
-    default :cookbook_sync_threads, 10
-
-    # At the beginning of the Chef Client run, the cookbook manifests are downloaded which
-    # contain URLs for every file in every relevant cookbook.  Most of the files
-    # (recipes, resources, providers, libraries, etc) are immediately synchronized
-    # at the start of the run.  The handling of "files" and "templates" directories,
-    # however, have two modes of operation.  They can either all be downloaded immediately
-    # at the start of the run (no_lazy_load==true) or else they can be lazily loaded as
-    # cookbook_file or template resources are converged which require them (no_lazy_load==false).
-    #
-    # The advantage of lazily loading these files is that unnecessary files are not
-    # synchronized.  This may be useful to users with large files checked into cookbooks which
-    # are only selectively downloaded to a subset of clients which use the cookbook.  However,
-    # better solutions are to either isolate large files into individual cookbooks and only
-    # include those cookbooks in the run lists of the servers that need them -- or move to
-    # using remote_file and a more appropriate backing store like S3 for large file
-    # distribution.
+    # Override the default values that were set by Ohai.
     #
-    # The disadvantages of lazily loading files are that users some time find it
-    # confusing that their cookbooks are not fully synchronzied to the cache initially,
-    # and more importantly the time-sensitive URLs which are in the manifest may time
-    # out on long Chef runs before the resource that uses the file is converged
-    # (leading to many confusing 403 errors on template/cookbook_file resources).
-    #
-    default :no_lazy_load, true
-
-    # Default for the chef_gem compile_time attribute.  Nil is the same as true but will emit
-    # warnings on every use of chef_gem prompting the user to be explicit.  If the user sets this to
-    # true then the user will get backcompat behavior but with a single nag warning that cookbooks
-    # may break with this setting in the future.  The false setting is the recommended setting and
-    # will become the default.
-    default :chef_gem_compile_time, nil
-
-    # A whitelisted array of attributes you want sent over the wire when node
-    # data is saved.
-    # The default setting is nil, which collects all data. Setting to [] will not
-    # collect any data for save.
-    default :automatic_attribute_whitelist, nil
-    default :default_attribute_whitelist, nil
-    default :normal_attribute_whitelist, nil
-    default :override_attribute_whitelist, nil
-
-    config_context :windows_service do
-      # Set `watchdog_timeout` to the number of seconds to wait for a chef-client run
-      # to finish
-      default :watchdog_timeout, 2 * (60 * 60) # 2 hours
-    end
+    # REMOVEME once these configurables are removed from the top level of Ohai.
+    default :log_level, LOG_LEVEL
+    default :log_location, LOG_LOCATION
 
-    # Chef requires an English-language UTF-8 locale to function properly.  We attempt
-    # to use the 'locale -a' command and search through a list of preferences until we
-    # find one that we can use.  On Ubuntu systems we should find 'C.UTF-8' and be
-    # able to use that even if there is no English locale on the server, but Mac, Solaris,
-    # AIX, etc do not have that locale.  We then try to find an English locale and fall
-    # back to 'C' if we do not.  The choice of fallback is pick-your-poison.  If we try
-    # to do the work to return a non-US UTF-8 locale then we fail inside of providers when
-    # things like 'svn info' return Japanese and we can't parse them.  OTOH, if we pick 'C' then
-    # we will blow up on UTF-8 characters.  Between the warn we throw and the Encoding
-    # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by
-    # default rather than drop English.
+    # Ohai::Config[:log_level] is deprecated and warns when set. Unfortunately,
+    # there is no way to distinguish between setting log_level and setting
+    # Ohai::Config[:log_level]. Since log_level and log_location are used by
+    # chef-client and other tools (e.g., knife), we will mute the warnings here
+    # by redefining the config_attr_writer to not warn for these options.
     #
-    # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly
-    # available English UTF-8 locale.  However, all modern POSIXen should support 'locale -a'.
-    def self.guess_internal_locale
-      # https://github.com/opscode/chef/issues/2181
-      # Some systems have the `locale -a` command, but the result has
-      # invalid characters for the default encoding.
-      #
-      # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8",
-      # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding.
-      locales = shell_out_with_systems_locale!("locale -a").stdout.split
-      case
-      when locales.include?('C.UTF-8')
-        'C.UTF-8'
-      when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8')
-        'en_US.UTF-8'
-      when locales.include?('en.UTF-8')
-        'en.UTF-8'
-      else
-        # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8
-        guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i }
-        unless guesses.empty?
-          guessed_locale = guesses.first
-          # Transform into the form en_ZZ.UTF-8
-          guessed_locale.gsub(/UTF-?8$/i, "UTF-8")
-        else
-          Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support."
-          'C'
-        end
-      end
-    rescue
-      if Chef::Platform.windows?
-        Chef::Log.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else."
-      else
-        Chef::Log.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
+    # REMOVEME once the warnings for these configurables are removed from Ohai.
+    [ :log_level, :log_location ].each do |option|
+      config_attr_writer option do |value|
+        value
       end
-      'en_US.UTF-8'
     end
 
-    default :internal_locale, guess_internal_locale
-
-    # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g.
-    # japanese windows encodings).  If we do not do this, then knife upload will fail when a cookbook's
-    # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been
-    # passed.  Effectively, the Chef Ecosystem is globally UTF-8 by default.  Anyone who wants to be
-    # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with
-    # magic tags to make ruby correctly identify the encoding being used.  Changing this default will
-    # break Chef community cookbooks and is very highly discouraged.
-    default :ruby_encoding, Encoding::UTF_8
-
-    # If installed via an omnibus installer, this gives the path to the
-    # "embedded" directory which contains all of the software packaged with
-    # omnibus. This is used to locate the cacert.pem file on windows.
-    def self.embedded_dir
-      Pathname.new(_this_file).ascend do |path|
-        if path.basename.to_s == "embedded"
-          return path.to_s
-        end
-      end
-
-      nil
-    end
-
-    # Path to this file in the current install.
-    def self._this_file
-      File.expand_path(__FILE__)
-    end
   end
 end
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/constants.rb
similarity index 67%
copy from lib/chef/resource/whyrun_safe_ruby_block.rb
copy to lib/chef/constants.rb
index 6fa5383..d39ce4c 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/constants.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Phil Dibowitz (<phild at fb.com>)
-# Copyright:: Copyright (c) 2013 Facebook
+# Author:: John Keiser <jkeiser at chef.io>
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,17 +14,14 @@
 # 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
-    class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :whyrun_safe_ruby_block
-      end
-
-    end
+  NOT_PASSED = Object.new
+  def NOT_PASSED.to_s
+    "NOT_PASSED"
+  end
+  def NOT_PASSED.inspect
+    to_s
   end
+  NOT_PASSED.freeze
 end
diff --git a/lib/chef/cookbook/cookbook_collection.rb b/lib/chef/cookbook/cookbook_collection.rb
index ae63abf..38784c2 100644
--- a/lib/chef/cookbook/cookbook_collection.rb
+++ b/lib/chef/cookbook/cookbook_collection.rb
@@ -1,7 +1,7 @@
 #--
 # Author:: Tim Hinderliter (<tim at opscode.com>)
 # Author:: Christopher Walters (<cw at opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -41,5 +41,18 @@ class Chef
       cookbook_versions.each{ |cookbook_name, cookbook_version| self[cookbook_name] = cookbook_version }
     end
 
+    # Validates that the cookbook metadata allows it to run on this instance.
+    #
+    # Currently checks chef_version and ohai_version in the cookbook metadata
+    # against the running Chef::VERSION and Ohai::VERSION.
+    #
+    # @raises [Chef::Exceptions::CookbookChefVersionMismatch] if the Chef::VERSION fails validation
+    # @raises [Chef::Exceptions::CookbookOhaiVersionMismatch] if the Ohai::VERSION fails validation
+    def validate!
+      each do |cookbook_name, cookbook_version|
+        cookbook_version.metadata.validate_chef_version!
+        cookbook_version.metadata.validate_ohai_version!
+      end
+    end
   end
 end
diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb
index bcbfcbe..dbccdbc 100644
--- a/lib/chef/cookbook/cookbook_version_loader.rb
+++ b/lib/chef/cookbook/cookbook_version_loader.rb
@@ -91,7 +91,7 @@ class Chef
         remove_ignored_files
 
         if empty?
-          Chef::Log.warn "found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
+          Chef::Log.warn "Found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
         end
         @cookbook_settings
       end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index 781d3b4..e9509be 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -2,7 +2,7 @@
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: AJ Christensen (<aj at opscode.com>)
 # Author:: Seth Falcon (<seth at opscode.com>)
-# Copyright:: Copyright 2008-2010 Opscode, Inc.
+# Copyright:: Copyright 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -54,19 +54,24 @@ class Chef
       VERSION                = 'version'.freeze
       SOURCE_URL             = 'source_url'.freeze
       ISSUES_URL             = 'issues_url'.freeze
+      PRIVACY                = 'privacy'.freeze
+      CHEF_VERSIONS          = 'chef_versions'.freeze
+      OHAI_VERSIONS          = 'ohai_versions'.freeze
 
       COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer,
                             :maintainer_email, :license, :platforms, :dependencies,
                             :recommendations, :suggestions, :conflicting, :providing,
                             :replacing, :attributes, :groupings, :recipes, :version,
-                            :source_url, :issues_url ]
+                            :source_url, :issues_url, :privacy, :chef_versions, :ohai_versions ]
 
-      VERSION_CONSTRAINTS = {:depends     => DEPENDENCIES,
-                             :recommends  => RECOMMENDATIONS,
-                             :suggests    => SUGGESTIONS,
-                             :conflicts   => CONFLICTING,
-                             :provides    => PROVIDING,
-                             :replaces    => REPLACING }
+      VERSION_CONSTRAINTS = {:depends      => DEPENDENCIES,
+                             :recommends   => RECOMMENDATIONS,
+                             :suggests     => SUGGESTIONS,
+                             :conflicts    => CONFLICTING,
+                             :provides     => PROVIDING,
+                             :replaces     => REPLACING,
+                             :chef_version => CHEF_VERSIONS,
+                             :ohai_version => OHAI_VERSIONS }
 
       include Chef::Mixin::ParamsValidate
       include Chef::Mixin::FromFile
@@ -83,6 +88,11 @@ class Chef
       attr_reader :recipes
       attr_reader :version
 
+      # @return [Array<Gem::Dependency>] Array of supported Chef versions
+      attr_reader :chef_versions
+      # @return [Array<Gem::Dependency>] Array of supported Ohai versions
+      attr_reader :ohai_versions
+
       # Builds a new Chef::Cookbook::Metadata object.
       #
       # === Parameters
@@ -116,6 +126,9 @@ class Chef
         @version = Version.new("0.0.0")
         @source_url = ''
         @issues_url = ''
+        @privacy = false
+        @chef_versions = []
+        @ohai_versions = []
 
         @errors = []
       end
@@ -286,9 +299,13 @@ class Chef
       # === Returns
       # versions<Array>:: Returns the list of versions for the platform
       def depends(cookbook, *version_args)
-        version = new_args_format(:depends, cookbook, version_args)
-        constraint = validate_version_constraint(:depends, cookbook, version)
-        @dependencies[cookbook] = constraint.to_s
+        if cookbook == name
+          Chef::Log.warn "Ignoring self-dependency in cookbook #{name}, please remove it (in the future this will be fatal)."
+        else
+          version = new_args_format(:depends, cookbook, version_args)
+          constraint = validate_version_constraint(:depends, cookbook, version)
+          @dependencies[cookbook] = constraint.to_s
+        end
         @dependencies[cookbook]
       end
 
@@ -380,6 +397,28 @@ class Chef
         @replacing[cookbook]
       end
 
+      # Metadata DSL to set a valid chef_version.  May be declared multiple times
+      # with the result being 'OR'd such that if any statements match, the version
+      # is considered supported.  Uses Gem::Requirement for its implementation.
+      #
+      # @param version_args [Array<String>] Version constraint in String form
+      # @return [Array<Gem::Dependency>] Current chef_versions array
+      def chef_version(*version_args)
+        @chef_versions << Gem::Dependency.new('chef', *version_args) unless version_args.empty?
+        @chef_versions
+      end
+
+      # Metadata DSL to set a valid ohai_version.  May be declared multiple times
+      # with the result being 'OR'd such that if any statements match, the version
+      # is considered supported.  Uses Gem::Requirement for its implementation.
+      #
+      # @param version_args [Array<String>] Version constraint in String form
+      # @return [Array<Gem::Dependency>] Current ohai_versions array
+      def ohai_version(*version_args)
+        @ohai_versions << Gem::Dependency.new('ohai', *version_args) unless version_args.empty?
+        @ohai_versions
+      end
+
       # Adds a description for a recipe.
       #
       # === Parameters
@@ -450,7 +489,8 @@ class Chef
             :recipes => { :kind_of => [ Array ], :default => [] },
             :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] },
             :source_url => { :kind_of => String },
-            :issues_url => { :kind_of => String }
+            :issues_url => { :kind_of => String },
+            :privacy => { :kind_of => [ TrueClass, FalseClass ] }
           }
         )
         options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil?
@@ -474,6 +514,40 @@ class Chef
         @groupings[name]
       end
 
+      # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to an Array.
+      #
+      # Gem::Dependencey#to_s is not useful, and there is no #to_json defined on it or its component
+      # objets, so we have to write our own rendering method.
+      #
+      # [ Gem::Dependency.new(">= 12.5"), Gem::Dependency.new(">= 11.18.0", "< 12.0") ]
+      #
+      # results in:
+      #
+      # [ [ ">= 12.5" ], [ ">= 11.18.0", "< 12.0" ] ]
+      #
+      # @param deps [Array<Gem::Dependency>] Multiple Gem-style version constraints
+      # @return [Array<Array<String>]] Simple object representation of version constraints (for json)
+      def gem_requirements_to_array(*deps)
+        deps.map do |dep|
+          dep.requirement.requirements.map do |op, version|
+            "#{op} #{version}"
+          end.sort
+        end
+      end
+
+      # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to a hash.
+      #
+      # This is the inverse of #gem_requirements_to_array
+      #
+      # @param what [String] What version constraint we are constructing ('chef' or 'ohai' presently)
+      # @param array [Array<Array<String>]] Simple object representation of version constraints (from json)
+      # @return [Array<Gem::Dependency>] Multiple Gem-style version constraints
+      def gem_requirements_from_array(what, array)
+        array.map do |dep|
+          Gem::Dependency.new(what, *dep)
+        end
+      end
+
       def to_hash
         {
           NAME                   => self.name,
@@ -494,7 +568,10 @@ class Chef
           RECIPES                => self.recipes,
           VERSION                => self.version,
           SOURCE_URL             => self.source_url,
-          ISSUES_URL             => self.issues_url
+          ISSUES_URL             => self.issues_url,
+          PRIVACY                => self.privacy,
+          CHEF_VERSIONS          => gem_requirements_to_array(*self.chef_versions),
+          OHAI_VERSIONS          => gem_requirements_to_array(*self.ohai_versions)
         }
       end
 
@@ -528,6 +605,9 @@ class Chef
         @version                      = o[VERSION] if o.has_key?(VERSION)
         @source_url                   = o[SOURCE_URL] if o.has_key?(SOURCE_URL)
         @issues_url                   = o[ISSUES_URL] if o.has_key?(ISSUES_URL)
+        @privacy                      = o[PRIVACY] if o.has_key?(PRIVACY)
+        @chef_versions                = gem_requirements_from_array("chef", o[CHEF_VERSIONS]) if o.has_key?(CHEF_VERSIONS)
+        @ohai_versions                = gem_requirements_from_array("ohai", o[OHAI_VERSIONS]) if o.has_key?(OHAI_VERSIONS)
         self
       end
 
@@ -586,8 +666,60 @@ class Chef
         )
       end
 
+      #
+      # Sets the cookbook's privacy flag, or returns it.
+      #
+      # === Parameters
+      # privacy<TrueClass,FalseClass>:: Whether this cookbook is private or not
+      #
+      # === Returns
+      # privacy<TrueClass,FalseClass>:: Whether this cookbook is private or not
+      #
+      def privacy(arg=nil)
+        set_or_return(
+          :privacy,
+          arg,
+          :kind_of => [ TrueClass, FalseClass ]
+        )
+      end
+
+      # Validates that the Ohai::VERSION of the running chef-client matches one of the
+      # configured ohai_version statements in this cookbooks metadata.
+      #
+      # @raises [Chef::Exceptions::CookbookOhaiVersionMismatch] if the cookbook fails validation
+      def validate_ohai_version!
+        unless gem_dep_matches?("ohai", Gem::Version.new(Ohai::VERSION), *ohai_versions)
+          raise Exceptions::CookbookOhaiVersionMismatch.new(Ohai::VERSION, name, version, *ohai_versions)
+        end
+      end
+
+      # Validates that the Chef::VERSION of the running chef-client matches one of the
+      # configured chef_version statements in this cookbooks metadata.
+      #
+      # @raises [Chef::Exceptions::CookbookChefVersionMismatch] if the cookbook fails validation
+      def validate_chef_version!
+        unless gem_dep_matches?("chef", Gem::Version.new(Chef::VERSION), *chef_versions)
+          raise Exceptions::CookbookChefVersionMismatch.new(Chef::VERSION, name, version, *chef_versions)
+        end
+      end
+
     private
 
+      # Helper to match a gem style version (ohai_version/chef_version) against a set of
+      # Gem::Dependency version constraints.  If none are present, it always matches.  if
+      # multiple are present, one must match.  Returns false if none matches.
+      #
+      # @param what [String] the name of the constraint (e.g. 'chef' or 'ohai')
+      # @param version [String] the version to compare against the constraints
+      # @param deps [Array<Gem::Dependency>] Multiple Gem-style version constraints
+      # @return [Boolean] true if no constraints or a match, false if no match
+      def gem_dep_matches?(what, version, *deps)
+        # always match if we have no chef_version at all
+        return true unless deps.length > 0
+        # match if we match any of the chef_version lines
+        deps.any? { |dep| dep.match?(what, version) }
+      end
+
       def run_validation
         if name.nil?
           @errors = ["The `name' attribute is required in cookbook metadata"]
@@ -603,7 +735,7 @@ class Chef
           msg=<<-OBSOLETED
 The dependency specification syntax you are using is no longer valid. You may not
 specify more than one version constraint for a particular cookbook.
-Consult http://wiki.opscode.com/display/chef/Metadata for the updated syntax.
+Consult https://docs.chef.io/config_rb_metadata.html for the updated syntax.
 
 Called by: #{caller_name} '#{dep_name}', #{version_constraints.map {|vc| vc.inspect}.join(", ")}
 Called from:
@@ -622,7 +754,7 @@ OBSOLETED
 The version constraint syntax you are using is not valid. If you recently
 upgraded to Chef 0.10.0, be aware that you no may longer use "<<" and ">>" for
 'less than' and 'greater than'; use '<' and '>' instead.
-Consult http://wiki.opscode.com/display/chef/Metadata for more information.
+Consult https://docs.chef.io/config_rb_metadata.html for more information.
 
 Called by: #{caller_name} '#{dep_name}', '#{constraint_str}'
 Called from:
diff --git a/lib/chef/cookbook/remote_file_vendor.rb b/lib/chef/cookbook/remote_file_vendor.rb
index 9d895b1..7868430 100644
--- a/lib/chef/cookbook/remote_file_vendor.rb
+++ b/lib/chef/cookbook/remote_file_vendor.rb
@@ -69,7 +69,7 @@ class Chef
           Chef::FileCache.move_to(raw_file.path, cache_filename)
         else
           Chef::Log.debug("Not fetching #{cache_filename}, as the cache is up to date.")
-          Chef::Log.debug("current checksum: #{current_checksum}; manifest checksum: #{found_manifest_record['checksum']})")
+          Chef::Log.debug("Current checksum: #{current_checksum}; manifest checksum: #{found_manifest_record['checksum']})")
         end
 
         full_path_cache_filename = Chef::FileCache.load(cache_filename, false)
diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb
index 1b96d05..fc8e739 100644
--- a/lib/chef/cookbook/synchronizer.rb
+++ b/lib/chef/cookbook/synchronizer.rb
@@ -131,7 +131,7 @@ class Chef
       files_remaining_by_cookbook[file.cookbook] -= 1
 
       if files_remaining_by_cookbook[file.cookbook] == 0
-        @events.synchronized_cookbook(file.cookbook.name)
+        @events.synchronized_cookbook(file.cookbook.name, file.cookbook)
       end
     end
 
diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb
index c05fedb..79005b1 100644
--- a/lib/chef/cookbook_loader.rb
+++ b/lib/chef/cookbook_loader.rb
@@ -106,7 +106,7 @@ class Chef
       if @cookbooks_by_name.has_key?(cookbook.to_sym) or load_cookbook(cookbook.to_sym)
         @cookbooks_by_name[cookbook.to_sym]
       else
-        raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (http://wiki.opscode.com/display/chef/Metadata)"
+        raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (https://docs.chef.io/config_rb_metadata.html)"
       end
     end
 
diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb
index 9e7a55c..2be189e 100644
--- a/lib/chef/cookbook_site_streaming_uploader.rb
+++ b/lib/chef/cookbook_site_streaming_uploader.rb
@@ -26,7 +26,7 @@ require 'openssl'
 class Chef
   # == Chef::CookbookSiteStreamingUploader
   # A streaming multipart HTTP upload implementation. Used to upload cookbooks
-  # (in tarball form) to http://cookbooks.opscode.com
+  # (in tarball form) to https://supermarket.chef.io
   #
   # inspired by http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
   class CookbookSiteStreamingUploader
@@ -106,7 +106,7 @@ class Chef
 
         url = URI.parse(to_url)
 
-        Chef::Log.logger.debug("Signing: method: #{http_verb}, path: #{url.path}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
+        Chef::Log.logger.debug("Signing: method: #{http_verb}, url: #{url}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
 
         # We use the body for signing the request if the file parameter
         # wasn't a valid file or wasn't included. Extract the body (with
@@ -141,13 +141,8 @@ class Chef
         req.content_type = 'multipart/form-data; boundary=' + boundary unless parts.empty?
         req.body_stream = body_stream
 
-        http = Net::HTTP.new(url.host, url.port)
-        if url.scheme == "https"
-          http.use_ssl = true
-          http.verify_mode = verify_mode
-        end
+        http = Chef::HTTP::BasicClient.new(url).http_client
         res = http.request(req)
-        #res = http.start {|http_proc| http_proc.request(req) }
 
         # alias status to code and to_s to body for test purposes
         # TODO: stop the following madness!
@@ -166,17 +161,6 @@ class Chef
         res
       end
 
-      private
-
-      def verify_mode
-        verify_mode = Chef::Config[:ssl_verify_mode]
-        if verify_mode == :verify_none
-          OpenSSL::SSL::VERIFY_NONE
-        elsif verify_mode == :verify_peer
-          OpenSSL::SSL::VERIFY_PEER
-        end
-      end
-
     end
 
     class StreamPart
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index 8d302ee..0e9617f 100644
--- a/lib/chef/cookbook_version.rb
+++ b/lib/chef/cookbook_version.rb
@@ -4,7 +4,7 @@
 # Author:: Tim Hinderliter (<tim at opscode.com>)
 # Author:: Seth Falcon (<seth at opscode.com>)
 # Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright 2008-2011 Opscode, Inc.
+# Copyright:: Copyright 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -51,12 +51,12 @@ class Chef
     attr_accessor :metadata_filenames
 
     def status=(new_status)
-      Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed")
+      Chef.log_deprecation("Deprecated method `status' called. This method will be removed.")
       @status = new_status
     end
 
     def status
-      Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed")
+      Chef.log_deprecation("Deprecated method `status' called. This method will be removed.")
       @status
     end
 
@@ -450,7 +450,11 @@ class Chef
         end
         relative_search_path.map {|relative_path| File.join(segment.to_s, relative_path)}
       else
-        [File.join(segment, path)]
+        if segment.to_sym == :root_files
+          [path]
+        else
+          [File.join(segment, path)]
+        end
       end
     end
     private :preferences_for_path
@@ -480,7 +484,7 @@ class Chef
     # @deprecated This method was used by the Ruby Chef Server and is no longer
     #   needed. There is no replacement.
     def generate_manifest_with_urls(&url_generator)
-      Chef::Log.deprecation("Deprecated method #generate_manifest_with_urls called from #{caller(1).first}")
+      Chef.log_deprecation("Deprecated method #generate_manifest_with_urls.")
 
       rendered_manifest = manifest.dup
       COOKBOOK_SEGMENTS.each do |segment|
diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb
index 8475774..401ba6f 100644
--- a/lib/chef/data_bag.rb
+++ b/lib/chef/data_bag.rb
@@ -144,7 +144,7 @@ class Chef
     def save
       begin
         if Chef::Config[:why_run]
-          Chef::Log.warn("In whyrun mode, so NOT performing data bag save.")
+          Chef::Log.warn("In why-run mode, so NOT performing data bag save.")
         else
           create
         end
diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb
index 9f92e26..31c9b69 100644
--- a/lib/chef/data_bag_item.rb
+++ b/lib/chef/data_bag_item.rb
@@ -170,7 +170,7 @@ class Chef
       r = chef_server_rest
       begin
         if Chef::Config[:why_run]
-          Chef::Log.warn("In whyrun mode, so NOT performing data bag item save.")
+          Chef::Log.warn("In why-run mode, so NOT performing data bag item save.")
         else
           r.put_rest("data/#{data_bag}/#{item_id}", self)
         end
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/delayed_evaluator.rb
similarity index 67%
copy from lib/chef/resource/whyrun_safe_ruby_block.rb
copy to lib/chef/delayed_evaluator.rb
index 6fa5383..9f18a53 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/delayed_evaluator.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Phil Dibowitz (<phild at fb.com>)
-# Copyright:: Copyright (c) 2013 Facebook
+# Author:: John Keiser <jkeiser at chef.io>
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,17 +14,8 @@
 # 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
-    class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :whyrun_safe_ruby_block
-      end
-
-    end
+  class DelayedEvaluator < Proc
   end
 end
diff --git a/lib/chef/deprecation/mixin/template.rb b/lib/chef/deprecation/mixin/template.rb
index 36d18ad..58a661c 100644
--- a/lib/chef/deprecation/mixin/template.rb
+++ b/lib/chef/deprecation/mixin/template.rb
@@ -25,7 +25,7 @@ class Chef
       # == Deprecation::Provider::Mixin::Template
       # This module contains the deprecated functions of
       # Chef::Mixin::Template. These functions are refactored to different
-      # components. They are frozen and will be removed in Chef 12.
+      # components. They are frozen and will be removed in Chef 13.
       #
 
       module Template
@@ -46,4 +46,3 @@ class Chef
     end
   end
 end
-
diff --git a/lib/chef/deprecation/provider/cookbook_file.rb b/lib/chef/deprecation/provider/cookbook_file.rb
index dfbf4a3..92f5ce3 100644
--- a/lib/chef/deprecation/provider/cookbook_file.rb
+++ b/lib/chef/deprecation/provider/cookbook_file.rb
@@ -24,7 +24,7 @@ class Chef
       # == Deprecation::Provider::CookbookFile
       # This module contains the deprecated functions of
       # Chef::Provider::CookbookFile. These functions are refactored to
-      # different components. They are frozen and will be removed in Chef 12.
+      # different components. They are frozen and will be removed in Chef 13.
       #
       module CookbookFile
 
diff --git a/lib/chef/deprecation/provider/file.rb b/lib/chef/deprecation/provider/file.rb
index 125f31f..31038ab 100644
--- a/lib/chef/deprecation/provider/file.rb
+++ b/lib/chef/deprecation/provider/file.rb
@@ -25,7 +25,7 @@ class Chef
       # == Deprecation::Provider::File
       # This module contains the deprecated functions of
       # Chef::Provider::File. These functions are refactored to different
-      # components. They are frozen and will be removed in Chef 12.
+      # components. They are frozen and will be removed in Chef 13.
       #
       module File
 
diff --git a/lib/chef/deprecation/provider/remote_directory.rb b/lib/chef/deprecation/provider/remote_directory.rb
new file mode 100644
index 0000000..cc8026e
--- /dev/null
+++ b/lib/chef/deprecation/provider/remote_directory.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013-2015 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.
+#
+
+class Chef
+  module Deprecation
+    module Provider
+      module RemoteDirectory
+
+        def directory_root_in_cookbook_cache
+          Chef.log_deprecation "the Chef::Provider::RemoteDirectory#directory_root_in_cookbook_cache method is deprecated"
+
+          @directory_root_in_cookbook_cache ||=
+            begin
+              cookbook = run_context.cookbook_collection[resource_cookbook]
+              cookbook.preferred_filename_on_disk_location(node, :files, source, path)
+            end
+        end
+
+        # List all excluding . and ..
+        def ls(path)
+          files = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'),
+                           ::File::FNM_DOTMATCH)
+
+          # Remove current directory and previous directory
+          files = files.reject do |name|
+            basename = Pathname.new(name).basename().to_s
+            ['.', '..'].include?(basename)
+          end
+
+          # Clean all the paths... this is required because of the join
+          files.map {|f| Chef::Util::PathHelper.cleanpath(f)}
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/deprecation/provider/remote_file.rb b/lib/chef/deprecation/provider/remote_file.rb
index 4452de6..c06a5cc 100644
--- a/lib/chef/deprecation/provider/remote_file.rb
+++ b/lib/chef/deprecation/provider/remote_file.rb
@@ -23,7 +23,7 @@ class Chef
       # == Deprecation::Provider::RemoteFile
       # This module contains the deprecated functions of
       # Chef::Provider::RemoteFile. These functions are refactored to different
-      # components. They are frozen and will be removed in Chef 12.
+      # components. They are frozen and will be removed in Chef 13.
       #
       module RemoteFile
 
@@ -83,4 +83,3 @@ class Chef
     end
   end
 end
-
diff --git a/lib/chef/deprecation/provider/template.rb b/lib/chef/deprecation/provider/template.rb
index d7a228e..34e5f54 100644
--- a/lib/chef/deprecation/provider/template.rb
+++ b/lib/chef/deprecation/provider/template.rb
@@ -25,7 +25,7 @@ class Chef
       # == Deprecation::Provider::Template
       # This module contains the deprecated functions of
       # Chef::Provider::Template. These functions are refactored to different
-      # components. They are frozen and will be removed in Chef 12.
+      # components. They are frozen and will be removed in Chef 13.
       #
       module Template
 
diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb
index 34f468f..0b1ec2d 100644
--- a/lib/chef/deprecation/warnings.rb
+++ b/lib/chef/deprecation/warnings.rb
@@ -25,10 +25,9 @@ class Chef
           m = instance_method(name)
           define_method(name) do |*args|
             message = []
-            message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 12."
-            message << "Please update your cookbooks accordingly. Accessed from:"
-            caller[0..3].each {|l| message << l}
-            Chef::Log.deprecation message
+            message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 13."
+            message << "Please update your cookbooks accordingly."
+            Chef.log_deprecation(message)
             super(*args)
           end
         end
diff --git a/lib/chef/digester.rb b/lib/chef/digester.rb
index 75c4e76..f2b496b 100644
--- a/lib/chef/digester.rb
+++ b/lib/chef/digester.rb
@@ -38,7 +38,11 @@ class Chef
     end
 
     def generate_checksum(file)
-      checksum_file(file, OpenSSL::Digest::SHA256.new)
+      if file.is_a?(StringIO)
+        checksum_io(file, OpenSSL::Digest::SHA256.new)
+      else
+        checksum_file(file, OpenSSL::Digest::SHA256.new)
+      end
     end
 
     def self.generate_md5_checksum_for_file(*args)
diff --git a/lib/chef/dsl/chef_provisioning.rb b/lib/chef/dsl/chef_provisioning.rb
new file mode 100644
index 0000000..84a3db3
--- /dev/null
+++ b/lib/chef/dsl/chef_provisioning.rb
@@ -0,0 +1,57 @@
+#
+# Author:: John Keiser (<jkeiser at chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, 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.
+#
+
+class Chef
+  module DSL
+    # Lazy activation for the chef-provisioning gem. Specifically, we set up methods for
+    # each resource and DSL method in chef-provisioning which, when invoked, will
+    # require 'chef-provisioning' (which will define the actual method) and then call the
+    # method chef-provisioning defined.
+    module ChefProvisioning
+      %w(
+        add_machine_options
+        current_image_options
+        current_machine_options
+        load_balancer
+        machine_batch
+        machine_execute
+        machine_file
+        machine_image
+        machine
+        with_driver
+        with_image_options
+        with_machine_options
+      ).each do |method_name|
+        eval(<<-EOM, binding, __FILE__, __LINE__+1)
+          def #{method_name}(*args, &block)
+            Chef::DSL::ChefProvisioning.load_chef_provisioning
+            self.#{method_name}(*args, &block)
+          end
+        EOM
+      end
+
+      def self.load_chef_provisioning
+        # Remove all chef-provisioning methods; they will be added back in by chef-provisioning
+        public_instance_methods(false).each do |method_name|
+          remove_method(method_name)
+        end
+        require 'chef/provisioning'
+      end
+    end
+  end
+end
diff --git a/lib/chef/dsl/cheffish.rb b/lib/chef/dsl/cheffish.rb
new file mode 100644
index 0000000..304c4df
--- /dev/null
+++ b/lib/chef/dsl/cheffish.rb
@@ -0,0 +1,64 @@
+#
+# Author:: John Keiser (<jkeiser at chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, 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.
+#
+
+class Chef
+  module DSL
+    # Lazy activation for the cheffish gem. Specifically, we set up methods for
+    # each resource and DSL method in cheffish which, when invoked, will
+    # require 'cheffish' (which will define the actual method) and then call the
+    # method cheffish defined.
+    module Cheffish
+      %w(
+        chef_acl
+        chef_client
+        chef_container
+        chef_data_bag
+        chef_environment
+        chef_group
+        chef_mirror
+        chef_node
+        chef_organization
+        chef_role
+        chef_user
+        private_key
+        public_key
+        with_chef_data_bag
+        with_chef_environment
+        with_chef_data_bag_item_encryption
+        with_chef_server
+        with_chef_local_server
+        get_private_key
+      ).each do |method_name|
+        eval(<<-EOM, binding, __FILE__, __LINE__+1)
+          def #{method_name}(*args, &block)
+            Chef::DSL::Cheffish.load_cheffish
+            self.#{method_name}(*args, &block)
+          end
+        EOM
+      end
+
+      def self.load_cheffish
+        # Remove all cheffish methods; they will be added back in by cheffish
+        public_instance_methods(false).each do |method_name|
+          remove_method(method_name)
+        end
+        require 'cheffish'
+      end
+    end
+  end
+end
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/declare_resource.rb
similarity index 51%
copy from lib/chef/dsl/recipe.rb
copy to lib/chef/dsl/declare_resource.rb
index c22f053..52b0cb7 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/declare_resource.rb
@@ -1,7 +1,7 @@
 #--
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Christopher Walters (<cw at opscode.com>)
-# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2008, 2009-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,62 +17,11 @@
 # limitations under the License.
 #
 
-require 'chef/mixin/convert_to_class_name'
 require 'chef/exceptions'
-require 'chef/resource_builder'
-require 'chef/mixin/shell_out'
 
 class Chef
   module DSL
-
-    # == Chef::DSL::Recipe
-    # Provides the primary recipe DSL functionality for defining Chef resource
-    # objects via method calls.
-    module Recipe
-
-      include Chef::Mixin::ShellOut
-      include Chef::Mixin::ConvertToClassName
-
-      def method_missing(method_symbol, *args, &block)
-        # If we have a definition that matches, we want to use that instead.  This should
-        # let you do some really crazy over-riding of "native" types, if you really want
-        # to.
-        if has_resource_definition?(method_symbol)
-          evaluate_resource_definition(method_symbol, *args, &block)
-        elsif have_resource_class_for?(method_symbol)
-          # Otherwise, we're rocking the regular resource call route.
-          declare_resource(method_symbol, args[0], caller[0], &block)
-        else
-          begin
-            super
-          rescue NoMethodError
-            raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}"
-          rescue NameError
-            raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
-          end
-        end
-      end
-
-      def has_resource_definition?(name)
-        run_context.definitions.has_key?(name)
-      end
-
-      # Processes the arguments and block as a resource definition.
-      def evaluate_resource_definition(definition_name, *args, &block)
-
-        # This dupes the high level object, but we still need to dup the params
-        new_def = run_context.definitions[definition_name].dup
-
-        new_def.params = new_def.params.dup
-        new_def.node = run_context.node
-        # This sets up the parameter overrides
-        new_def.instance_eval(&block) if block
-
-        new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context)
-        new_recipe.params = new_def.params
-        new_recipe.params[:name] = args[0]
-        new_recipe.instance_eval(&new_def.recipe)
-      end
+    module DeclareResource
 
       #
       # Instantiates a resource (via #build_resource), then adds it to the
@@ -99,9 +48,17 @@ class Chef
       #     action :delete
       #   end
       #
-      def declare_resource(type, name, created_at=nil, &resource_attrs_block)
+      def declare_resource(type, name, created_at=nil, run_context: self.run_context, create_if_missing: false, &resource_attrs_block)
         created_at ||= caller[0]
 
+        if create_if_missing
+          begin
+            resource = run_context.resource_collection.find(type => name)
+            return resource
+          rescue Chef::Exceptions::ResourceNotFound
+          end
+        end
+
         resource = build_resource(type, name, created_at, &resource_attrs_block)
 
         run_context.resource_collection.insert(resource, resource_type: type, instance_name: name)
@@ -129,8 +86,11 @@ class Chef
       #     action :delete
       #   end
       #
-      def build_resource(type, name, created_at=nil, &resource_attrs_block)
+      def build_resource(type, name, created_at=nil, run_context: self.run_context, &resource_attrs_block)
         created_at ||= caller[0]
+        Thread.exclusive do
+          require 'chef/resource_builder' unless defined?(Chef::ResourceBuilder)
+        end
 
         Chef::ResourceBuilder.new(
           type:                type,
@@ -143,39 +103,6 @@ class Chef
           enclosing_provider:  self.is_a?(Chef::Provider) ? self :  nil
         ).build(&resource_attrs_block)
       end
-
-      def resource_class_for(snake_case_name)
-        Chef::Resource.resource_for_node(snake_case_name, run_context.node)
-      end
-
-      def have_resource_class_for?(snake_case_name)
-        not resource_class_for(snake_case_name).nil?
-      rescue NameError
-        false
-      end
-
-      def describe_self_for_error
-        if respond_to?(:name)
-          %Q[`#{self.class.name} "#{name}"']
-        elsif respond_to?(:recipe_name)
-          %Q[`#{self.class.name} "#{recipe_name}"']
-        else
-          to_s
-        end
-      end
-
-      def exec(args)
-        raise Chef::Exceptions::ResourceNotFound, "exec was called, but you probably meant to use an execute resource.  If not, please call Kernel#exec explicitly.  The exec block called was \"#{args}\""
-      end
-
     end
   end
 end
-
-# We require this at the BOTTOM of this file to avoid circular requires (it is used
-# at runtime but not load time)
-require 'chef/resource'
-
-# **DEPRECATED**
-# This used to be part of chef/mixin/recipe_definition_dsl_core. Load the file to activate the deprecation code.
-require 'chef/mixin/recipe_definition_dsl_core'
diff --git a/lib/chef/dsl/definitions.rb b/lib/chef/dsl/definitions.rb
new file mode 100644
index 0000000..1358f67
--- /dev/null
+++ b/lib/chef/dsl/definitions.rb
@@ -0,0 +1,44 @@
+class Chef
+  module DSL
+    #
+    # Module containing a method for each declared definition
+    #
+    # Depends on declare_resource(name, created_at, &block)
+    #
+    # @api private
+    #
+    module Definitions
+      def self.add_definition(dsl_name)
+        module_eval <<-EOM, __FILE__, __LINE__+1
+          def #{dsl_name}(*args, &block)
+            evaluate_resource_definition(#{dsl_name.inspect}, *args, &block)
+          end
+        EOM
+      end
+
+      # @api private
+      def has_resource_definition?(name)
+        run_context.definitions.has_key?(name)
+      end
+
+      # Processes the arguments and block as a resource definition.
+      #
+      # @api private
+      def evaluate_resource_definition(definition_name, *args, &block)
+
+        # This dupes the high level object, but we still need to dup the params
+        new_def = run_context.definitions[definition_name].dup
+
+        new_def.params = new_def.params.dup
+        new_def.node = run_context.node
+        # This sets up the parameter overrides
+        new_def.instance_eval(&block) if block
+
+        new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context)
+        new_recipe.params = new_def.params
+        new_recipe.params[:name] = args[0]
+        new_recipe.instance_eval(&new_def.recipe)
+      end
+    end
+  end
+end
diff --git a/lib/chef/dsl/platform_introspection.rb b/lib/chef/dsl/platform_introspection.rb
index 2a52010..a6bd12d 100644
--- a/lib/chef/dsl/platform_introspection.rb
+++ b/lib/chef/dsl/platform_introspection.rb
@@ -50,7 +50,7 @@ class Chef
 
         def value_for_node(node)
           platform, version = node[:platform].to_s, node[:platform_version].to_s
-          # Check if we match a version constraint via Chef::VersionConstraint and Chef::Version::Platform
+          # Check if we match a version constraint via Chef::VersionConstraint::Platform and Chef::Version::Platform
           matched_value = match_versions(node)
           if @values.key?(platform) && @values[platform].key?(version)
             @values[platform][version]
@@ -76,11 +76,11 @@ class Chef
             keys = @values[platform].keys
             keys.each do |k|
               begin
-                if Chef::VersionConstraint.new(k).include?(node_version)
+                if Chef::VersionConstraint::Platform.new(k).include?(node_version)
                   key_matches << k
                 end
               rescue Chef::Exceptions::InvalidVersionConstraint => e
-                Chef::Log.debug "Caught InvalidVersionConstraint. This means that a key in value_for_platform cannot be interpreted as a Chef::VersionConstraint."
+                Chef::Log.debug "Caught InvalidVersionConstraint. This means that a key in value_for_platform cannot be interpreted as a Chef::VersionConstraint::Platform."
                 Chef::Log.debug(e)
               end
             end
diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb
index 7af67e9..3d84b29 100644
--- a/lib/chef/dsl/reboot_pending.rb
+++ b/lib/chef/dsl/reboot_pending.rb
@@ -45,11 +45,12 @@ class Chef
           registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') ||
 
           # Vista + Server 2008 and newer may have reboots pending from CBS
-          registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') ||
+          registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') ||
 
           # The mere existence of the UpdateExeVolatile key should indicate a pending restart for certain updates
           # http://support.microsoft.com/kb/832475
-          (registry_key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') &&
+          Chef::Platform.windows_server_2003? &&
+                (registry_key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') &&
                 !registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0].nil? &&
                 [1,2,3].include?(registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0][:data]))
         elsif platform?("ubuntu")
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index c22f053..441fcbb 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -1,7 +1,7 @@
 #--
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Christopher Walters (<cw at opscode.com>)
-# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2008, 2009-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,10 +17,12 @@
 # limitations under the License.
 #
 
-require 'chef/mixin/convert_to_class_name'
 require 'chef/exceptions'
-require 'chef/resource_builder'
 require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
+require 'chef/dsl/resources'
+require 'chef/dsl/definitions'
+require 'chef/dsl/declare_resource'
 
 class Chef
   module DSL
@@ -31,118 +33,11 @@ class Chef
     module Recipe
 
       include Chef::Mixin::ShellOut
-      include Chef::Mixin::ConvertToClassName
+      include Chef::Mixin::PowershellOut
 
-      def method_missing(method_symbol, *args, &block)
-        # If we have a definition that matches, we want to use that instead.  This should
-        # let you do some really crazy over-riding of "native" types, if you really want
-        # to.
-        if has_resource_definition?(method_symbol)
-          evaluate_resource_definition(method_symbol, *args, &block)
-        elsif have_resource_class_for?(method_symbol)
-          # Otherwise, we're rocking the regular resource call route.
-          declare_resource(method_symbol, args[0], caller[0], &block)
-        else
-          begin
-            super
-          rescue NoMethodError
-            raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}"
-          rescue NameError
-            raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
-          end
-        end
-      end
-
-      def has_resource_definition?(name)
-        run_context.definitions.has_key?(name)
-      end
-
-      # Processes the arguments and block as a resource definition.
-      def evaluate_resource_definition(definition_name, *args, &block)
-
-        # This dupes the high level object, but we still need to dup the params
-        new_def = run_context.definitions[definition_name].dup
-
-        new_def.params = new_def.params.dup
-        new_def.node = run_context.node
-        # This sets up the parameter overrides
-        new_def.instance_eval(&block) if block
-
-        new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context)
-        new_recipe.params = new_def.params
-        new_recipe.params[:name] = args[0]
-        new_recipe.instance_eval(&new_def.recipe)
-      end
-
-      #
-      # Instantiates a resource (via #build_resource), then adds it to the
-      # resource collection. Note that resource classes are looked up directly,
-      # so this will create the resource you intended even if the method name
-      # corresponding to that resource has been overridden.
-      #
-      # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
-      # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
-      # @param created_at [String] The caller of the resource.  Use `caller[0]`
-      #   to get the caller of your function.  Defaults to the caller of this
-      #   function.
-      # @param resource_attrs_block A block that lets you set attributes of the
-      #   resource (it is instance_eval'd on the resource instance).
-      #
-      # @return [Chef::Resource] The new resource.
-      #
-      # @example
-      #   declare_resource(:file, '/x/y.txy', caller[0]) do
-      #     action :delete
-      #   end
-      #   # Equivalent to
-      #   file '/x/y.txt' do
-      #     action :delete
-      #   end
-      #
-      def declare_resource(type, name, created_at=nil, &resource_attrs_block)
-        created_at ||= caller[0]
-
-        resource = build_resource(type, name, created_at, &resource_attrs_block)
-
-        run_context.resource_collection.insert(resource, resource_type: type, instance_name: name)
-        resource
-      end
-
-      #
-      # Instantiate a resource of the given +type+ with the given +name+ and
-      # attributes as given in the +resource_attrs_block+.
-      #
-      # The resource is NOT added to the resource collection.
-      #
-      # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
-      # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
-      # @param created_at [String] The caller of the resource.  Use `caller[0]`
-      #   to get the caller of your function.  Defaults to the caller of this
-      #   function.
-      # @param resource_attrs_block A block that lets you set attributes of the
-      #   resource (it is instance_eval'd on the resource instance).
-      #
-      # @return [Chef::Resource] The new resource.
-      #
-      # @example
-      #   build_resource(:file, '/x/y.txy', caller[0]) do
-      #     action :delete
-      #   end
-      #
-      def build_resource(type, name, created_at=nil, &resource_attrs_block)
-        created_at ||= caller[0]
-
-        Chef::ResourceBuilder.new(
-          type:                type,
-          name:                name,
-          created_at:          created_at,
-          params:              @params,
-          run_context:         run_context,
-          cookbook_name:       cookbook_name,
-          recipe_name:         recipe_name,
-          enclosing_provider:  self.is_a?(Chef::Provider) ? self :  nil
-        ).build(&resource_attrs_block)
-      end
+      include Chef::DSL::Resources
+      include Chef::DSL::Definitions
+      include Chef::DSL::DeclareResource
 
       def resource_class_for(snake_case_name)
         Chef::Resource.resource_for_node(snake_case_name, run_context.node)
@@ -156,9 +51,9 @@ class Chef
 
       def describe_self_for_error
         if respond_to?(:name)
-          %Q[`#{self.class.name} "#{name}"']
+          %Q[`#{self.class} "#{name}"']
         elsif respond_to?(:recipe_name)
-          %Q[`#{self.class.name} "#{recipe_name}"']
+          %Q[`#{self.class} "#{recipe_name}"']
         else
           to_s
         end
@@ -168,12 +63,70 @@ class Chef
         raise Chef::Exceptions::ResourceNotFound, "exec was called, but you probably meant to use an execute resource.  If not, please call Kernel#exec explicitly.  The exec block called was \"#{args}\""
       end
 
+      # DEPRECATED:
+      # method_missing must live for backcompat purposes until Chef 13.
+      def method_missing(method_symbol, *args, &block)
+        #
+        # If there is already DSL for this, someone must have called
+        # method_missing manually. Not a fan. Not. A. Fan.
+        #
+        if respond_to?(method_symbol)
+          Chef.log_deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13. Use public_send() or send() instead.")
+          return send(method_symbol, *args, &block)
+        end
+
+        #
+        # If a definition exists, then Chef::DSL::Definitions.add_definition was
+        # never called.  DEPRECATED.
+        #
+        if run_context.definitions.has_key?(method_symbol.to_sym)
+          Chef.log_deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}).  This will become required in Chef 13.")
+          Chef::DSL::Definitions.add_definition(method_symbol)
+          return send(method_symbol, *args, &block)
+        end
+
+        #
+        # See if the resource exists anyway.  If the user had set
+        # Chef::Resource::Blah = <resource>, a deprecation warning will be
+        # emitted and the DSL method 'blah' will be added to the DSL.
+        #
+        resource_class = Chef::ResourceResolver.resolve(method_symbol, node: run_context ? run_context.node : nil)
+        if resource_class
+          Chef::DSL::Resources.add_resource_dsl(method_symbol)
+          return send(method_symbol, *args, &block)
+        end
+
+        begin
+          super
+        rescue NoMethodError
+          raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}"
+        rescue NameError
+          raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
+        end
+      end
+
+      module FullDSL
+        require 'chef/dsl/data_query'
+        require 'chef/dsl/platform_introspection'
+        require 'chef/dsl/include_recipe'
+        require 'chef/dsl/registry_helper'
+        require 'chef/dsl/reboot_pending'
+        require 'chef/dsl/audit'
+        require 'chef/dsl/powershell'
+        include Chef::DSL::DataQuery
+        include Chef::DSL::PlatformIntrospection
+        include Chef::DSL::IncludeRecipe
+        include Chef::DSL::Recipe
+        include Chef::DSL::RegistryHelper
+        include Chef::DSL::RebootPending
+        include Chef::DSL::Audit
+        include Chef::DSL::Powershell
+      end
     end
   end
 end
 
-# We require this at the BOTTOM of this file to avoid circular requires (it is used
-# at runtime but not load time)
+# Avoid circular references for things that are only used in instance methods
 require 'chef/resource'
 
 # **DEPRECATED**
diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb
new file mode 100644
index 0000000..3d582da
--- /dev/null
+++ b/lib/chef/dsl/resources.rb
@@ -0,0 +1,57 @@
+#
+# Author:: John Keiser <jkeiser at chef.io>
+# Copyright:: Copyright (c) 2015 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/dsl/cheffish'
+require 'chef/dsl/chef_provisioning'
+
+class Chef
+  module DSL
+    #
+    # Module containing a method for each globally declared Resource
+    #
+    # Depends on declare_resource(name, created_at, &block)
+    #
+    # @api private
+    module Resources
+      # Include the lazy loaders for cheffish and chef-provisioning, so that the
+      # resource DSL is there but the gems aren't activated yet.
+      include Chef::DSL::Cheffish
+      include Chef::DSL::ChefProvisioning
+
+      def self.add_resource_dsl(dsl_name)
+        begin
+          module_eval(<<-EOM, __FILE__, __LINE__+1)
+            def #{dsl_name}(*args, &block)
+              Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1
+              declare_resource(#{dsl_name.inspect}, args[0], caller[0], &block)
+            end
+          EOM
+        rescue SyntaxError
+          # Handle the case where dsl_name has spaces, etc.
+          define_method(dsl_name.to_sym) do |*args, &block|
+            Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1
+            declare_resource(dsl_name, args[0], caller[0], &block)
+          end
+        end
+      end
+      def self.remove_resource_dsl(dsl_name)
+          remove_method(dsl_name)
+      rescue NameError
+      end
+    end
+  end
+end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 7274105..585a3db 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -47,14 +47,19 @@ class Chef
       def ohai_completed(node)
       end
 
-      # Already have a client key, assuming this node has registered.
+      # Announce that we're not going to register the client. Generally because
+      # we already have the private key, or because we're deliberately not using
+      # a key.
       def skipping_registration(node_name, config)
       end
 
-      # About to attempt to register as +node_name+
+      # About to attempt to create a private key registered to the server with
+      # client +node_name+.
       def registration_start(node_name, config)
       end
 
+      # Successfully created the private key and registered this client with the
+      # server.
       def registration_completed
       end
 
@@ -82,6 +87,11 @@ class Chef
       def node_load_completed(node, expanded_run_list, config)
       end
 
+      # Called after the Policyfile was loaded. This event only occurs when
+      # chef is in policyfile mode.
+      def policyfile_loaded(policy)
+      end
+
       # Called before the cookbook collection is fetched from the server.
       def cookbook_resolution_start(expanded_run_list)
       end
@@ -113,8 +123,8 @@ class Chef
       def cookbook_sync_start(cookbook_count)
       end
 
-      # Called when cookbook +cookbook_name+ has been sync'd
-      def synchronized_cookbook(cookbook_name)
+      # Called when cookbook +cookbook+ has been sync'd
+      def synchronized_cookbook(cookbook_name, cookbook)
       end
 
       # Called when an individual file in a cookbook has been updated
@@ -239,13 +249,13 @@ class Chef
       end
 
       # Called when audit phase successfully finishes
-      def audit_phase_complete
+      def audit_phase_complete(audit_output)
       end
 
       # Called if there is an uncaught exception during the audit phase.  The audit runner should
       # be catching and handling errors from the examples, so this is only uncaught errors (like
       # bugs in our handling code)
-      def audit_phase_failed(exception)
+      def audit_phase_failed(exception, audit_output)
       end
 
       # Signifies the start of a `control_group` block with a defined name
@@ -264,26 +274,37 @@ class Chef
       # def notifications_resolved
       # end
 
+      #
+      # Resource events and ordering:
+      #
+      # 1. Start the action
+      #    - resource_action_start
+      # 2. Check the guard
+      #    - resource_skipped: (goto 7) if only_if/not_if say to skip
+      # 3. Load the current resource
+      #    - resource_current_state_loaded
+      #    - resource_current_state_load_bypassed (if not why-run safe)
+      # 4. Check if why-run safe
+      #    - resource_bypassed: (goto 7) if not why-run safe
+      # 5. During processing:
+      #    - resource_update_applied: For each actual change (many per action)
+      # 6. Processing complete status:
+      #    - resource_failed if the resource threw an exception while running
+      #    - resource_failed_retriable: (goto 3) if resource failed and will be retried
+      #    - resource_updated if the resource was updated (resource_update_applied will have been called)
+      #    - resource_up_to_date if the resource was up to date (no resource_update_applied)
+      # 7. Processing complete:
+      #    - resource_completed
+      #
+
       # Called before action is executed on a resource.
       def resource_action_start(resource, action, notification_type=nil, notifier=nil)
       end
 
-      # Called when a resource fails, but will retry.
-      def resource_failed_retriable(resource, action, retry_count, exception)
-      end
-
-      # Called when a resource fails and will not be retried.
-      def resource_failed(resource, action, exception)
-      end
-
       # Called when a resource action has been skipped b/c of a conditional
       def resource_skipped(resource, action, conditional)
       end
 
-      # Called when a resource action has been completed
-      def resource_completed(resource)
-      end
-
       # Called after #load_current_resource has run.
       def resource_current_state_loaded(resource, action, current_resource)
       end
@@ -297,21 +318,33 @@ class Chef
       def resource_bypassed(resource, action, current_resource)
       end
 
-      # Called when a resource has no converge actions, e.g., it was already correct.
-      def resource_up_to_date(resource, action)
-      end
-
       # Called when a change has been made to a resource. May be called multiple
       # times per resource, e.g., a file may have its content updated, and then
       # its permissions updated.
       def resource_update_applied(resource, action, update)
       end
 
+      # Called when a resource fails, but will retry.
+      def resource_failed_retriable(resource, action, retry_count, exception)
+      end
+
+      # Called when a resource fails and will not be retried.
+      def resource_failed(resource, action, exception)
+      end
+
       # Called after a resource has been completely converged, but only if
       # modifications were made.
       def resource_updated(resource, action)
       end
 
+      # Called when a resource has no converge actions, e.g., it was already correct.
+      def resource_up_to_date(resource, action)
+      end
+
+      # Called when a resource action has been completed
+      def resource_completed(resource)
+      end
+
       # A stream has opened.
       def stream_opened(stream, options = {})
       end
@@ -347,8 +380,12 @@ class Chef
       def whyrun_assumption(action, resource, message)
       end
 
-      ## TODO: deprecation warning. this way we can queue them up and present
-      #  them all at once.
+      # Emit a message about something being deprecated.
+      def deprecation(message, location=caller(2..2)[0])
+      end
+
+      def run_list_expanded(run_list_expansion)
+      end
 
       # An uncategorized message. This supports the case that a user needs to
       # pass output that doesn't fit into one of the callbacks above. Note that
diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb
index 9f43f14..ad7df46 100644
--- a/lib/chef/event_dispatch/dispatcher.rb
+++ b/lib/chef/event_dispatch/dispatcher.rb
@@ -9,6 +9,8 @@ class Chef
     # the registered subscribers.
     class Dispatcher < Base
 
+      attr_reader :subscribers
+
       def initialize(*subscribers)
         @subscribers = subscribers
       end
@@ -18,23 +20,43 @@ class Chef
         @subscribers << subscriber
       end
 
+      # Check to see if we are dispatching to a formatter
+      def formatter?
+        @subscribers.any? { |s| s.respond_to?(:is_formatter?) && s.is_formatter? }
+      end
+
       ####
       # All messages are unconditionally forwarded to all subscribers, so just
       # define the forwarding in one go:
       #
 
-      # Define a method that will be forwarded to all
-      def self.def_forwarding_method(method_name)
-        define_method(method_name) do |*args|
-          @subscribers.each { |s| s.send(method_name, *args) }
+      def call_subscribers(method_name, *args)
+        @subscribers.each do |s|
+          # Skip new/unsupported event names.
+          next if !s.respond_to?(method_name)
+          mth = s.method(method_name)
+          # Trim arguments to match what the subscriber expects to allow
+          # adding new arguments without breaking compat.
+          if mth.arity < args.size && mth.arity >= 0
+            mth.call(*args.take(mth.arity))
+          else
+            mth.call(*args)
+          end
         end
       end
 
       (Base.instance_methods - Object.instance_methods).each do |method_name|
-        def_forwarding_method(method_name)
+        class_eval <<-EOM
+          def #{method_name}(*args)
+            call_subscribers(#{method_name.inspect}, *args)
+          end
+        EOM
       end
 
+      # Special case deprecation, since it needs to know its caller
+      def deprecation(message, location=caller(2..2)[0])
+        call_subscribers(:deprecation, message, location)
+      end
     end
   end
 end
-
diff --git a/lib/chef/event_dispatch/dsl.rb b/lib/chef/event_dispatch/dsl.rb
new file mode 100644
index 0000000..c6f21c9
--- /dev/null
+++ b/lib/chef/event_dispatch/dsl.rb
@@ -0,0 +1,64 @@
+#
+# Author:: Ranjib Dey (<ranjib at linux.com>)
+# Copyright:: Copyright (c) 2015 Ranjib Dey
+# 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/event_dispatch/base'
+require 'chef/exceptions'
+require 'chef/config'
+
+class Chef
+  module EventDispatch
+    class DSL
+      attr_reader :handler
+
+      def initialize(name)
+        klass = Class.new(Chef::EventDispatch::Base) do
+          attr_reader :name
+        end
+        @handler = klass.new
+        @handler.instance_variable_set(:@name, name)
+
+        # Use event.register API to add anonymous handler if Chef.run_context
+        # and associated event dispatcher is set, else fallback to
+        # Chef::Config[:hanlder]
+        if Chef.run_context && Chef.run_context.events
+          Chef::Log.debug("Registering handler '#{name}' using events api")
+          Chef.run_context.events.register(handler)
+        else
+          Chef::Log.debug("Registering handler '#{name}' using global config")
+          Chef::Config[:event_handlers] << handler
+        end
+      end
+
+      # Adds a new event handler derived from base handler
+      # with user defined block against a chef event
+      #
+      # @return [Chef::EventDispatch::Base] a base handler object
+      def on(event_type, &block)
+        validate!(event_type)
+        handler.define_singleton_method(event_type) do |*args|
+          instance_exec(*args, &block)
+        end
+      end
+
+      private
+      def validate!(event_type)
+        all_event_types = (Chef::EventDispatch::Base.instance_methods - Object.instance_methods)
+        raise Chef::Exceptions::InvalidEventType, "Invalid event type: #{event_type}" unless all_event_types.include?(event_type)
+      end
+    end
+  end
+end
diff --git a/lib/chef/event_dispatch/events_output_stream.rb b/lib/chef/event_dispatch/events_output_stream.rb
index 8de9b0f..d9c2164 100644
--- a/lib/chef/event_dispatch/events_output_stream.rb
+++ b/lib/chef/event_dispatch/events_output_stream.rb
@@ -21,6 +21,14 @@ class Chef
         events.stream_output(self, str, options)
       end
 
+      def <<(str)
+        events.stream_output(self, str, options)
+      end
+
+      def write(str)
+        events.stream_output(self, str, options)
+      end
+
       def close
         events.stream_closed(self, options)
       end
diff --git a/lib/chef/event_loggers/windows_eventlog.rb b/lib/chef/event_loggers/windows_eventlog.rb
index 37dcdc8..7a3a28b 100644
--- a/lib/chef/event_loggers/windows_eventlog.rb
+++ b/lib/chef/event_loggers/windows_eventlog.rb
@@ -18,17 +18,7 @@
 
 require 'chef/event_loggers/base'
 require 'chef/platform/query_helpers'
-
-if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
-  if defined? Windows::Constants
-    [:INFINITE, :WAIT_FAILED, :FORMAT_MESSAGE_IGNORE_INSERTS, :ERROR_INSUFFICIENT_BUFFER].each do |c|
-      # These are redefined in 'win32/eventlog'
-      Windows::Constants.send(:remove_const, c) if Windows::Constants.const_defined? c
-    end
-  end
-
-  require 'win32/eventlog'
-end
+require 'chef/win32/eventlog'
 
 class Chef
   module EventLoggers
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index eea6a2f..0f4e74a 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -2,7 +2,7 @@
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Seth Falcon (<seth at opscode.com>)
 # Author:: Kyle Goodwin (<kgoodwin at primerevenue.com>)
-# Copyright:: Copyright 2008-2010 Opscode, Inc.
+# Copyright:: Copyright 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,12 +17,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+require 'chef-config/exceptions'
+
 class Chef
   # == Chef::Exceptions
   # Chef's custom exceptions are all contained within the Chef::Exceptions
   # namespace.
   class Exceptions
 
+    ConfigurationError = ChefConfig::ConfigurationError
+
     # Backcompat with Chef::ShellOut code:
     require 'mixlib/shellout/exceptions'
 
@@ -68,11 +72,22 @@ class Chef
     class DuplicateRole < RuntimeError; end
     class ValidationFailed < ArgumentError; end
     class InvalidPrivateKey < ArgumentError; end
-    class ConfigurationError < ArgumentError; end
+    class MissingKeyAttribute < ArgumentError; end
+    class KeyCommandInputError < ArgumentError; end
+    class BootstrapCommandInputError < ArgumentError
+      def initialize
+        super "You cannot pass both --json-attributes and --json-attribute-file. Please pass one or none."
+      end
+    end
+    class InvalidKeyArgument < ArgumentError; end
+    class InvalidKeyAttribute < ArgumentError; end
+    class InvalidUserAttribute < ArgumentError; end
+    class InvalidClientAttribute < ArgumentError; end
     class RedirectLimitExceeded < RuntimeError; end
     class AmbiguousRunlistSpecification < ArgumentError; end
     class CookbookFrozen < ArgumentError; end
     class CookbookNotFound < RuntimeError; end
+    class OnlyApiVersion0SupportedForAction < RuntimeError; end
     # Cookbook loader used to raise an argument error when cookbook not found.
     # for back compat, need to raise an error that inherits from ArgumentError
     class CookbookNotFoundInRepo < ArgumentError; end
@@ -90,7 +105,14 @@ class Chef
     class ConflictingMembersInGroup < ArgumentError; end
     class InvalidResourceReference < RuntimeError; end
     class ResourceNotFound < RuntimeError; end
+    class ProviderNotFound < RuntimeError; end
+    NoProviderAvailable = ProviderNotFound
     class VerificationNotFound < RuntimeError; end
+    class InvalidEventType < ArgumentError; end
+    class MultipleIdentityError < RuntimeError; end
+    # Used in Resource::ActionClass#load_current_resource to denote that
+    # the resource doesn't actually exist (for example, the file does not exist)
+    class CurrentValueDoesNotExist < RuntimeError; end
 
     # Can't find a Resource of this type that is valid on this platform.
     class NoSuchResourceType < NameError
@@ -99,6 +121,8 @@ class Chef
       end
     end
 
+    class InvalidPolicybuilderCall < ArgumentError; end
+
     class InvalidResourceSpecification < ArgumentError; end
     class SolrConnectionError < RuntimeError; end
     class IllegalChecksumRevert < RuntimeError; end
@@ -112,6 +136,23 @@ class Chef
     class EnclosingDirectoryDoesNotExist < ArgumentError; end
     # Errors originating from calls to the Win32 API
     class Win32APIError < RuntimeError; end
+
+    class Win32NetAPIError < Win32APIError
+      attr_reader :msg, :error_code
+      def initialize(msg, error_code)
+        @msg = msg
+        @error_code = error_code
+
+        formatted_message = ""
+        formatted_message << "---- Begin Win32 API output ----\n"
+        formatted_message << "Net Api Error Code: #{error_code}\n"
+        formatted_message << "Net Api Error Message: #{msg}\n"
+        formatted_message << "---- End Win32 API output ----\n"
+
+        super(formatted_message)
+      end
+    end
+
     # Thrown when Win32 API layer binds to non-existent Win32 function.  Occurs
     # when older versions of Windows don't support newer Win32 API functions.
     class Win32APIFunctionNotImplemented < NotImplementedError; end
@@ -128,6 +169,7 @@ class Chef
     class LCMParser < RuntimeError; end
 
     class CannotDetermineHomebrewOwner < Package; end
+    class CannotDetermineWindowsInstallerType < Package; end
 
     # Can not create staging file during file deployment
     class FileContentStagingError < RuntimeError
@@ -211,8 +253,6 @@ class Chef
 
     class ChildConvergeError < RuntimeError; end
 
-    class NoProviderAvailable < RuntimeError; end
-
     class DeprecatedFeatureError < RuntimeError;
       def initalize(message)
         super("#{message} (raising error due to treat_deprecation_warnings_as_errors being set)")
@@ -431,7 +471,7 @@ class Chef
         wrapped_errors.each_with_index do |e,i|
           backtrace << "#{i+1}) #{e.class} -  #{e.message}"
           backtrace += e.backtrace if e.backtrace
-          backtrace << ""
+          backtrace << "" unless i == wrapped_errors.length - 1
         end
         set_backtrace(backtrace)
       end
@@ -443,6 +483,20 @@ class Chef
       end
     end
 
+    class CookbookChefVersionMismatch < RuntimeError
+      def initialize(chef_version, cookbook_name, cookbook_version, *constraints)
+        constraint_str = constraints.map { |c| c.requirement.as_list.to_s }.join(', ')
+        super "Cookbook '#{cookbook_name}' version '#{cookbook_version}' depends on chef version #{constraint_str}, but the running chef version is #{chef_version}"
+      end
+    end
+
+    class CookbookOhaiVersionMismatch < RuntimeError
+      def initialize(ohai_version, cookbook_name, cookbook_version, *constraints)
+        constraint_str = constraints.map { |c| c.requirement.as_list.to_s }.join(', ')
+        super "Cookbook '#{cookbook_name}' version '#{cookbook_version}' depends on ohai version #{constraint_str}, but the running ohai version is #{ohai_version}"
+      end
+    end
+
     class MultipleDscResourcesFound < RuntimeError
       attr_reader :resources_found
       def initialize(resources_found)
diff --git a/lib/chef/file_access_control/unix.rb b/lib/chef/file_access_control/unix.rb
index 472f30b..8178d5f 100644
--- a/lib/chef/file_access_control/unix.rb
+++ b/lib/chef/file_access_control/unix.rb
@@ -79,18 +79,18 @@ class Chef
       def should_update_owner?
         if target_uid.nil?
           # the user has not specified a permission on the new resource, so we never manage it with FAC
-          Chef::Log.debug("found target_uid == nil, so no owner was specified on resource, not managing owner")
+          Chef::Log.debug("Found target_uid == nil, so no owner was specified on resource, not managing owner")
           return false
         elsif current_uid.nil?
           # the user has specified a permission, and we are creating a file, so always enforce permissions
-          Chef::Log.debug("found current_uid == nil, so we are creating a new file, updating owner")
+          Chef::Log.debug("Found current_uid == nil, so we are creating a new file, updating owner")
           return true
         elsif target_uid != current_uid
           # the user has specified a permission, and it does not match the file, so fix the permission
-          Chef::Log.debug("found target_uid != current_uid, updating owner")
+          Chef::Log.debug("Found target_uid != current_uid, updating owner")
           return true
         else
-          Chef::Log.debug("found target_uid == current_uid, not updating owner")
+          Chef::Log.debug("Found target_uid == current_uid, not updating owner")
           # the user has specified a permission, but it matches the file, so behave idempotently
           return false
         end
@@ -138,18 +138,18 @@ class Chef
       def should_update_group?
         if target_gid.nil?
           # the user has not specified a permission on the new resource, so we never manage it with FAC
-          Chef::Log.debug("found target_gid == nil, so no group was specified on resource, not managing group")
+          Chef::Log.debug("Found target_gid == nil, so no group was specified on resource, not managing group")
           return false
         elsif current_gid.nil?
           # the user has specified a permission, and we are creating a file, so always enforce permissions
-          Chef::Log.debug("found current_gid == nil, so we are creating a new file, updating group")
+          Chef::Log.debug("Found current_gid == nil, so we are creating a new file, updating group")
           return true
         elsif target_gid != current_gid
           # the user has specified a permission, and it does not match the file, so fix the permission
-          Chef::Log.debug("found target_gid != current_gid, updating group")
+          Chef::Log.debug("Found target_gid != current_gid, updating group")
           return true
         else
-          Chef::Log.debug("found target_gid == current_gid, not updating group")
+          Chef::Log.debug("Found target_gid == current_gid, not updating group")
           # the user has specified a permission, but it matches the file, so behave idempotently
           return false
         end
@@ -187,18 +187,20 @@ class Chef
       def should_update_mode?
         if target_mode.nil?
           # the user has not specified a permission on the new resource, so we never manage it with FAC
-          Chef::Log.debug("found target_mode == nil, so no mode was specified on resource, not managing mode")
+          Chef::Log.debug("Found target_mode == nil, so no mode was specified on resource, not managing mode")
           return false
         elsif current_mode.nil?
           # the user has specified a permission, and we are creating a file, so always enforce permissions
-          Chef::Log.debug("found current_mode == nil, so we are creating a new file, updating mode")
+          Chef::Log.debug("Found current_mode == nil, so we are creating a new file, updating mode")
           return true
         elsif target_mode != current_mode
           # the user has specified a permission, and it does not match the file, so fix the permission
-          Chef::Log.debug("found target_mode != current_mode, updating mode")
+          Chef::Log.debug("Found target_mode != current_mode, updating mode")
+          return true
+        elsif suid_bit_set? and (should_update_group? or should_update_owner?)
           return true
         else
-          Chef::Log.debug("found target_mode == current_mode, not updating mode")
+          Chef::Log.debug("Found target_mode == current_mode, not updating mode")
           # the user has specified a permission, but it matches the file, so behave idempotently
           return false
         end
@@ -280,6 +282,9 @@ class Chef
         return nil
       end
 
+      def suid_bit_set?
+        return target_mode & 04000 > 0
+      end
     end
   end
 end
diff --git a/lib/chef/file_content_management/deploy/cp.rb b/lib/chef/file_content_management/deploy/cp.rb
index c6b1d6c..ea378c2 100644
--- a/lib/chef/file_content_management/deploy/cp.rb
+++ b/lib/chef/file_content_management/deploy/cp.rb
@@ -34,12 +34,12 @@ class Chef
       #
       class Cp
         def create(file)
-          Chef::Log.debug("touching #{file} to create it")
+          Chef::Log.debug("Touching #{file} to create it")
           FileUtils.touch(file)
         end
 
         def deploy(src, dst)
-          Chef::Log.debug("copying temporary file #{src} into place at #{dst}")
+          Chef::Log.debug("Copying temporary file #{src} into place at #{dst}")
           FileUtils.cp(src, dst)
         end
       end
diff --git a/lib/chef/file_content_management/deploy/mv_unix.rb b/lib/chef/file_content_management/deploy/mv_unix.rb
index 758c594..9712486 100644
--- a/lib/chef/file_content_management/deploy/mv_unix.rb
+++ b/lib/chef/file_content_management/deploy/mv_unix.rb
@@ -30,19 +30,19 @@ class Chef
         def create(file)
           # this is very simple, but it ensures that ownership and file modes take
           # good defaults, in particular mode needs to obey umask on create
-          Chef::Log.debug("touching #{file} to create it")
+          Chef::Log.debug("Touching #{file} to create it")
           FileUtils.touch(file)
         end
 
         def deploy(src, dst)
           # we are only responsible for content so restore the dst files perms
-          Chef::Log.debug("reading modes from #{dst} file")
+          Chef::Log.debug("Reading modes from #{dst} file")
           stat = ::File.stat(dst)
           mode = stat.mode & 07777
           uid  = stat.uid
           gid  = stat.gid
 
-          Chef::Log.debug("applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}")
+          Chef::Log.debug("Applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}")
 
           # i own the inode, so should be able to at least chmod it
           ::File.chmod(mode, src)
@@ -67,7 +67,7 @@ class Chef
             Chef::Log.warn("Could not set gid = #{gid} on #{src}, file modes not preserved")
           end
 
-          Chef::Log.debug("moving temporary file #{src} into place at #{dst}")
+          Chef::Log.debug("Moving temporary file #{src} into place at #{dst}")
           FileUtils.mv(src, dst)
         end
       end
diff --git a/lib/chef/file_content_management/deploy/mv_windows.rb b/lib/chef/file_content_management/deploy/mv_windows.rb
index 7504123..e2951db 100644
--- a/lib/chef/file_content_management/deploy/mv_windows.rb
+++ b/lib/chef/file_content_management/deploy/mv_windows.rb
@@ -35,7 +35,7 @@ class Chef
         ACL = Security::ACL
 
         def create(file)
-          Chef::Log.debug("touching #{file} to create it")
+          Chef::Log.debug("Touching #{file} to create it")
           FileUtils.touch(file)
         end
 
@@ -63,12 +63,22 @@ class Chef
             raise Chef::Exceptions::WindowsNotAdmin, "can not get the security information for '#{dst}' due to missing Administrator privileges."
           end
 
-          if dst_sd.dacl_present?
-            apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? })
+          dacl_present = dst_sd.dacl_present?
+          if dacl_present
+            if dst_sd.dacl.nil?
+              apply_dacl = nil
+            else
+              apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? })
+            end
           end
 
-          if dst_sd.sacl_present?
-            apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? })
+          sacl_present = dst_sd.sacl_present?
+          if sacl_present
+            if dst_sd.sacl.nil?
+              apply_sacl = nil
+            else
+              apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? })
+            end
           end
 
           #
@@ -84,8 +94,8 @@ class Chef
           dst_so = Security::SecurableObject.new(dst)
           dst_so.group = dst_sd.group
           dst_so.owner = dst_sd.owner
-          dst_so.set_dacl(apply_dacl, dst_sd.dacl_inherits?) if dst_sd.dacl_present?
-          dst_so.set_sacl(apply_sacl, dst_sd.sacl_inherits?) if dst_sd.sacl_present?
+          dst_so.set_dacl(apply_dacl, dst_sd.dacl_inherits?) if dacl_present
+          dst_so.set_sacl(apply_sacl, dst_sd.sacl_inherits?) if sacl_present
 
         end
       end
diff --git a/lib/chef/file_content_management/tempfile.rb b/lib/chef/file_content_management/tempfile.rb
index 2dde0ce..6e1624f 100644
--- a/lib/chef/file_content_management/tempfile.rb
+++ b/lib/chef/file_content_management/tempfile.rb
@@ -49,7 +49,7 @@ class Chef
           end
         end
 
-        raise Chef::Exceptions::FileContentStagingError(errors) if tf.nil?
+        raise Chef::Exceptions::FileContentStagingError, errors if tf.nil?
 
         # We always process the tempfile in binmode so that we
         # preserve the line endings of the content.
diff --git a/lib/chef/formatters/base.rb b/lib/chef/formatters/base.rb
index c901068..4c393f7 100644
--- a/lib/chef/formatters/base.rb
+++ b/lib/chef/formatters/base.rb
@@ -212,6 +212,13 @@ class Chef
         file_load_failed(path, exception)
       end
 
+      def deprecation(message, location=caller(2..2)[0])
+        Chef::Log.deprecation("#{message} at #{location}")
+      end
+
+      def is_formatter?
+        true
+      end
     end
 
 
@@ -222,6 +229,9 @@ class Chef
 
       cli_name(:null)
 
+      def is_formatter?
+        false
+      end
     end
 
   end
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index 7144d00..70108f5 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -3,9 +3,9 @@ require 'chef/config'
 
 class Chef
   module Formatters
-    #--
-    # TODO: not sold on the name, but the output is similar to what rspec calls
-    # "specdoc"
+
+    # Formatter similar to RSpec's documentation formatter. Uses indentation to
+    # show context.
     class Doc < Formatters::Base
 
       attr_reader :start_time, :end_time, :successful_audits, :failed_audits
@@ -22,18 +22,31 @@ class Chef
         @failed_audits = 0
         @start_time = Time.now
         @end_time = @start_time
+        @skipped_resources = 0
       end
 
       def elapsed_time
         end_time - start_time
       end
 
+      def pretty_elapsed_time
+        time = elapsed_time
+        if time < 60 then
+          message = Time.at(time).utc.strftime("%S seconds")
+        elsif time < 3600 then
+          message = Time.at(time).utc.strftime("%M minutes %S seconds")
+        else
+          message = Time.at(time).utc.strftime("%H hours %M minutes %S seconds")
+        end
+        message
+      end
+
       def run_start(version)
         puts_line "Starting Chef Client, version #{version}"
       end
 
       def total_resources
-        @up_to_date_resources + @updated_resources
+        @up_to_date_resources + @updated_resources + @skipped_resources
       end
 
       def total_audits
@@ -42,10 +55,30 @@ class Chef
 
       def run_completed(node)
         @end_time = Time.now
+        # Print out deprecations.
+        if !deprecations.empty?
+          puts_line ""
+          puts_line "Deprecated features used!"
+          deprecations.each do |message, locations|
+            if locations.size == 1
+              puts_line "  #{message} at #{locations.size} location:"
+            else
+              puts_line "  #{message} at #{locations.size} locations:"
+            end
+            locations.each do |location|
+              prefix = "    - "
+              Array(location).each do |line|
+                puts_line "#{prefix}#{line}"
+                prefix = "      "
+              end
+            end
+          end
+          puts_line ""
+        end
         if Chef::Config[:why_run]
           puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated"
         else
-          puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{elapsed_time} seconds"
+          puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{pretty_elapsed_time}"
           if total_audits > 0
             puts_line "  #{successful_audits}/#{total_audits} controls succeeded"
           end
@@ -57,7 +90,7 @@ class Chef
         if Chef::Config[:why_run]
           puts_line "Chef Client failed. #{@updated_resources} resources would have been updated"
         else
-          puts_line "Chef Client failed. #{@updated_resources} resources updated in #{elapsed_time} seconds"
+          puts_line "Chef Client failed. #{@updated_resources} resources updated in #{pretty_elapsed_time}"
           if total_audits > 0
             puts_line "  #{successful_audits} controls succeeded"
           end
@@ -93,6 +126,10 @@ class Chef
       def node_load_completed(node, expanded_run_list, config)
       end
 
+      def policyfile_loaded(policy)
+        puts_line "Using policy '#{policy["name"]}' at revision '#{policy["revision_id"]}'"
+      end
+
       # Called before the cookbook collection is fetched from the server.
       def cookbook_resolution_start(expanded_run_list)
         puts_line "resolving cookbooks for run list: #{expanded_run_list.inspect}"
@@ -128,9 +165,9 @@ class Chef
         indent
       end
 
-      # Called when cookbook +cookbook_name+ has been sync'd
-      def synchronized_cookbook(cookbook_name)
-        puts_line "- #{cookbook_name}"
+      # Called when cookbook +cookbook+ has been sync'd
+      def synchronized_cookbook(cookbook_name, cookbook)
+        puts_line "- #{cookbook.name} (#{cookbook.version})"
       end
 
       # Called when an individual file in a cookbook has been updated
@@ -175,17 +212,21 @@ class Chef
         puts_line "Starting audit phase"
       end
 
-      def audit_phase_complete
+      def audit_phase_complete(audit_output)
+        puts_line audit_output
         puts_line "Auditing complete"
       end
 
-      def audit_phase_failed(error)
+      def audit_phase_failed(error, audit_output)
+        puts_line audit_output
         puts_line ""
         puts_line "Audit phase exception:"
         indent
         puts_line "#{error.message}"
-        error.backtrace.each do |l|
-          puts_line l
+        if error.backtrace
+          error.backtrace.each do |l|
+            puts_line l
+          end
         end
       end
 
@@ -228,6 +269,7 @@ class Chef
 
       # Called when a resource action has been skipped b/c of a conditional
       def resource_skipped(resource, action, conditional)
+        @skipped_resources += 1
         # TODO: more info about conditional
         puts " (skipped due to #{conditional.short_description})", :stream => resource
         unindent
@@ -326,6 +368,16 @@ class Chef
         end
       end
 
+      def deprecation(message, location=caller(2..2)[0])
+        if Chef::Config[:treat_deprecation_warnings_as_errors]
+          super
+        end
+
+        # Save deprecations to the screen until the end
+        deprecations[message] ||= Set.new
+        deprecations[message] << location
+      end
+
       def indent
         indent_by(2)
       end
@@ -333,6 +385,12 @@ class Chef
       def unindent
         indent_by(-2)
       end
+
+      protected
+
+      def deprecations
+        @deprecations ||= {}
+      end
     end
   end
 end
diff --git a/lib/chef/formatters/error_inspectors/api_error_formatting.rb b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
index 652d478..05ee313 100644
--- a/lib/chef/formatters/error_inspectors/api_error_formatting.rb
+++ b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
@@ -16,6 +16,8 @@
 # limitations under the License.
 #
 
+require 'chef/http/authenticator'
+
 class Chef
   module Formatters
 
@@ -65,6 +67,24 @@ E
         error_description.section("Server Response:",format_rest_error)
       end
 
+      def describe_406_error(error_description, response)
+        if response["x-ops-server-api-version"]
+          version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
+          client_api_version = version_header["request_version"]
+          min_server_version = version_header["min_version"]
+          max_server_version = version_header["max_version"]
+
+          error_description.section("Incompatible server API version:",<<-E)
+This version of the API that this Chef request specified is not supported by the Chef server you sent this request to.
+The server supports a min API version of #{min_server_version} and a max API version of #{max_server_version}.
+Chef just made a request with an API version of #{client_api_version}.
+Please either update your Chef client or server to be a compatible set.
+E
+        else
+          describe_http_error(error_description)
+        end
+      end
+
       def describe_500_error(error_description)
         error_description.section("Unknown Server Error:",<<-E)
 The server had a fatal error attempting to load the node data.
diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
index 93328ad..621fadc 100644
--- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
@@ -30,19 +30,52 @@ class Chef
 
         def initialize(path, exception)
           @path, @exception = path, exception
+          @backtrace_lines_in_cookbooks = nil
+          @file_lines = nil
+          @culprit_backtrace_entry = nil
+          @culprit_line = nil
         end
 
         def add_explanation(error_description)
-          case exception
-          when Chef::Exceptions::RecipeNotFound
-            error_description.section(exception.class.name, exception.message)
-          else
-            error_description.section(exception.class.name, exception.message)
+          error_description.section(exception.class.name, exception.message)
 
+          if found_error_in_cookbooks?
             traceback = filtered_bt.map {|line| "  #{line}"}.join("\n")
             error_description.section("Cookbook Trace:", traceback)
             error_description.section("Relevant File Content:", context)
           end
+
+          if exception_message_modifying_frozen?
+            msg = <<-MESSAGE
+            Ruby objects are often frozen to prevent further modifications
+            when they would negatively impact the process (e.g. values inside
+            Ruby's ENV class) or to prevent polluting other objects when default
+            values are passed by reference to many instances of an object (e.g.
+            the empty Array as a Chef resource default, passed by reference
+            to every instance of the resource).
+
+            Chef uses Object#freeze to ensure the default values of properties
+            inside Chef resources are not modified, so that when a new instance
+            of a Chef resource is created, and Object#dup copies values by
+            reference, the new resource is not receiving a default value that
+            has been by a previous instance of that resource.
+
+            Instead of modifying an object that contains a default value for all
+            instances of a Chef resource, create a new object and assign it to
+            the resource's parameter, e.g.:
+
+            fruit_basket = resource(:fruit_basket, 'default')
+
+            # BAD: modifies 'contents' object for all new fruit_basket instances
+            fruit_basket.contents << 'apple'
+
+            # GOOD: allocates new array only owned by this fruit_basket instance
+            fruit_basket.contents %w(apple)
+
+            MESSAGE
+
+            error_description.section("Additional information:", msg.gsub(/^ {6}/, ''))
+          end
         end
 
         def context
@@ -75,7 +108,7 @@ class Chef
         def culprit_backtrace_entry
           @culprit_backtrace_entry ||= begin
              bt_entry = filtered_bt.first
-             Chef::Log.debug("backtrace entry for compile error: '#{bt_entry}'")
+             Chef::Log.debug("Backtrace entry for compile error: '#{bt_entry}'")
              bt_entry
           end
         end
@@ -93,10 +126,25 @@ class Chef
         end
 
         def filtered_bt
-          filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/ }
-          r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
-          Chef::Log.debug("filtered backtrace of compile error: #{r.join(",")}")
-          return r.count > 0 ? r : exception.backtrace
+          backtrace_lines_in_cookbooks.count > 0 ? backtrace_lines_in_cookbooks : exception.backtrace
+        end
+
+        def found_error_in_cookbooks?
+          !backtrace_lines_in_cookbooks.empty?
+        end
+
+        def backtrace_lines_in_cookbooks
+          @backtrace_lines_in_cookbooks ||=
+            begin
+              filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/i }
+              r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
+              Chef::Log.debug("Filtered backtrace of compile error: #{r.join(",")}")
+              r
+            end
+        end
+
+        def exception_message_modifying_frozen?
+          exception.message.include?("can't modify frozen")
         end
 
       end
diff --git a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
index aa5eb84..e011fa9 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
@@ -72,6 +72,8 @@ E
             describe_500_error(error_description)
           when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
             describe_503_error(error_description)
+          when Net::HTTPNotAcceptable
+            describe_406_error(error_description, response)
           else
             describe_http_error(error_description)
           end
diff --git a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
index 0cb849a..971dbd6 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
@@ -67,6 +67,8 @@ class Chef
             describe_500_error(error_description)
           when Net::HTTPBadGateway, Net::HTTPServiceUnavailable, Net::HTTPGatewayTimeOut
             describe_503_error(error_description)
+          when Net::HTTPNotAcceptable
+            describe_406_error(error_description, response)
           else
             describe_http_error(error_description)
           end
diff --git a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
index e257ee3..d81a9f7 100644
--- a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
@@ -84,6 +84,8 @@ E
             describe_500_error(error_description)
           when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
             describe_503_error(error_description)
+          when Net::HTTPNotAcceptable
+            describe_406_error(error_description, response)
           else
             describe_http_error(error_description)
           end
diff --git a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
index f31b348..dbd23f4 100644
--- a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
@@ -9,6 +9,8 @@ class Chef
       # TODO: Lots of duplication with the node_load_error_inspector, just
       # slightly tweaked to talk about validation keys instead of other keys.
       class RegistrationErrorInspector
+        include APIErrorFormatting
+
         attr_reader :exception
         attr_reader :node_name
         attr_reader :config
@@ -94,6 +96,8 @@ E
             error_description.section("Relevant Config Settings:",<<-E)
 chef_server_url "#{server_url}"
 E
+          when Net::HTTPNotAcceptable
+            describe_406_error(error_description, response)
           when Net::HTTPInternalServerError
             error_description.section("Unknown Server Error:",<<-E)
 The server had a fatal error attempting to load the node data.
diff --git a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
index 48572d9..6e4d932 100644
--- a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
@@ -63,7 +63,7 @@ class Chef
         def recipe_snippet
           return nil if dynamic_resource?
           @snippet ||= begin
-            if file = resource.source_line[/^(([\w]:)?[^:]+):([\d]+)/,1] and line = resource.source_line[/^#{file}:([\d]+)/,1].to_i
+            if file = parse_source and line = parse_line(file)
               return nil unless ::File.exists?(file)
               lines = IO.readlines(file)
 
@@ -111,6 +111,16 @@ class Chef
           line_nr_string + line
         end
 
+        def parse_source
+          resource.source_line[/^(([\w]:)?[^:]+):([\d]+)/,1]
+        end
+
+        def parse_line(source)
+          resource.source_line[/^#{Regexp.escape(source)}:([\d]+)/,1].to_i
+        end
+
+
+
       end
     end
   end
diff --git a/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb b/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
index ac19a98..8182282 100644
--- a/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
@@ -98,6 +98,8 @@ E
             error_description.section("Possible Causes:",<<-E)
 * Your client (#{username}) may have misconfigured authorization permissions.
 E
+          when Net::HTTPNotAcceptable
+            describe_406_error(error_description, response)
           when Net::HTTPInternalServerError
             error_description.section("Unknown Server Error:",<<-E)
 The server had a fatal error attempting to load a role.
diff --git a/lib/chef/formatters/indentable_output_stream.rb b/lib/chef/formatters/indentable_output_stream.rb
index 1beb286..f7f470b 100644
--- a/lib/chef/formatters/indentable_output_stream.rb
+++ b/lib/chef/formatters/indentable_output_stream.rb
@@ -50,6 +50,11 @@ class Chef
         print(string, from_args(args, :start_line => true, :end_line => true))
       end
 
+      # Print a raw chunk
+      def <<(obj)
+        print(obj)
+      end
+
       # Print a string.
       #
       # == Arguments
diff --git a/lib/chef/formatters/minimal.rb b/lib/chef/formatters/minimal.rb
index a189cc6..3862951 100644
--- a/lib/chef/formatters/minimal.rb
+++ b/lib/chef/formatters/minimal.rb
@@ -109,8 +109,8 @@ class Chef
         puts "Synchronizing cookbooks"
       end
 
-      # Called when cookbook +cookbook_name+ has been sync'd
-      def synchronized_cookbook(cookbook_name)
+      # Called when cookbook +cookbook+ has been sync'd
+      def synchronized_cookbook(cookbook_name, cookbook)
         print "."
       end
 
diff --git a/lib/chef/guard_interpreter/default_guard_interpreter.rb b/lib/chef/guard_interpreter/default_guard_interpreter.rb
index df91c2b..fead988 100644
--- a/lib/chef/guard_interpreter/default_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/default_guard_interpreter.rb
@@ -16,6 +16,8 @@
 # limitations under the License.
 #
 
+require 'chef/mixin/shell_out'
+
 class Chef
   class GuardInterpreter
     class DefaultGuardInterpreter
diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
index 1e2a534..8cff3bc 100644
--- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -68,7 +68,10 @@ class Chef
         run_action = action || @resource.action
 
         begin
-          @resource.run_action(run_action)
+          # Coerce to an array to be safe. This could happen with a legacy
+          # resource or something overriding the default_action code in a
+          # subclass.
+          Array(run_action).each {|action_to_run| @resource.run_action(action_to_run) }
           resource_updated = @resource.updated
         rescue Mixlib::ShellOut::ShellCommandFailed
           resource_updated = nil
@@ -92,8 +95,11 @@ class Chef
           raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Execute resource"
         end
 
+        # Duplicate the node below because the new RunContext
+        # overwrites the state of Node instances passed to it.
+        # See https://github.com/chef/chef/issues/3485.
         empty_events = Chef::EventDispatch::Dispatcher.new
-        anonymous_run_context = Chef::RunContext.new(parent_resource.node, {}, empty_events)
+        anonymous_run_context = Chef::RunContext.new(parent_resource.node.dup, {}, empty_events)
         interpreter_resource = resource_class.new('Guard resource', anonymous_run_context)
         interpreter_resource.is_guard_interpreter = true
 
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
index 16a826a..9f1eeed 100644
--- a/lib/chef/http.rb
+++ b/lib/chef/http.rb
@@ -5,7 +5,7 @@
 # Author:: Christopher Brown (<cb at opscode.com>)
 # Author:: Christopher Walters (<cw at opscode.com>)
 # Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2009, 2010, 2013 Opscode, Inc.
+# Copyright:: Copyright (c) 2009, 2010, 2013-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,7 +25,6 @@ require 'tempfile'
 require 'net/https'
 require 'uri'
 require 'chef/http/basic_client'
-require 'chef/http/socketless_chef_zero_client'
 require 'chef/monkey_patches/net_http'
 require 'chef/config'
 require 'chef/platform/query_helpers'
@@ -75,6 +74,7 @@ class Chef
     attr_reader :sign_on_redirect
     attr_reader :redirect_limit
 
+    attr_reader :options
     attr_reader :middlewares
 
     # Create a HTTP client object. The supplied +url+ is used as the base for
@@ -87,6 +87,7 @@ class Chef
       @sign_on_redirect = true
       @redirects_followed = 0
       @redirect_limit = 10
+      @options = options
 
       @middlewares = []
       self.class.middlewares.each do |middleware_class|
@@ -198,6 +199,14 @@ class Chef
     def http_client(base_url=nil)
       base_url ||= url
       if chef_zero_uri?(base_url)
+        # PERFORMANCE CRITICAL: *MUST* lazy require here otherwise we load up webrick
+        # via chef-zero and that hits DNS (at *require* time) which may timeout,
+        # when for most knife/chef-client work we never need/want this loaded.
+        Thread.exclusive {
+          unless defined?(SocketlessChefZeroClient)
+            require 'chef/http/socketless_chef_zero_client'
+          end
+        }
         SocketlessChefZeroClient.new(base_url)
       else
         BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
@@ -307,7 +316,7 @@ class Chef
           end
           return [response, request, return_value]
         end
-      rescue SocketError, Errno::ETIMEDOUT => e
+      rescue SocketError, Errno::ETIMEDOUT, Errno::ECONNRESET => e
         if http_retry_count - http_attempts + 1 > 0
           Chef::Log.error("Error connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
           sleep(http_retry_delay)
@@ -329,6 +338,13 @@ class Chef
           retry
         end
         raise Timeout::Error, "Timeout connecting to #{url}, giving up"
+      rescue OpenSSL::SSL::SSLError => e
+        if (http_retry_count - http_attempts + 1 > 0) && !e.message.include?("certificate verify failed")
+          Chef::Log.error("SSL Error connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
+          sleep(http_retry_delay)
+          retry
+        end
+        raise OpenSSL::SSL::SSLError, "SSL Error connecting to #{url} - #{e.message}"
       end
     end
 
diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb
index 4255f18..bffa9c4 100644
--- a/lib/chef/http/authenticator.rb
+++ b/lib/chef/http/authenticator.rb
@@ -24,6 +24,8 @@ class Chef
   class HTTP
     class Authenticator
 
+      DEFAULT_SERVER_API_VERSION = "1"
+
       attr_reader :signing_key_filename
       attr_reader :raw_key
       attr_reader :attr_names
@@ -37,10 +39,16 @@ class Chef
         @signing_key_filename = opts[:signing_key_filename]
         @key = load_signing_key(opts[:signing_key_filename], opts[:raw_key])
         @auth_credentials = AuthCredentials.new(opts[:client_name], @key)
+        if opts[:api_version]
+          @api_version = opts[:api_version]
+        else
+          @api_version = DEFAULT_SERVER_API_VERSION
+        end
       end
 
       def handle_request(method, url, headers={}, data=false)
         headers.merge!(authentication_headers(method, url, data)) if sign_requests?
+        headers.merge!({'X-Ops-Server-API-Version' => @api_version})
         [method, url, headers, data]
       end
 
diff --git a/lib/chef/http/basic_client.rb b/lib/chef/http/basic_client.rb
index 076d152..de5e7c0 100644
--- a/lib/chef/http/basic_client.rb
+++ b/lib/chef/http/basic_client.rb
@@ -101,12 +101,16 @@ class Chef
                 env["#{url.scheme.upcase}_PROXY"] || env["#{url.scheme}_proxy"]
 
         # Check if the proxy string contains a scheme. If not, add the url's scheme to the
-        # proxy before parsing. The regex /^.*:\/\// matches, for example, http://.
-        proxy = if proxy.match(/^.*:\/\//)
-          URI.parse(proxy)
-        else
-          URI.parse("#{url.scheme}://#{proxy}")
-        end if String === proxy
+        # proxy before parsing. The regex /^.*:\/\// matches, for example, http://. Reusing proxy
+        # here since we are really just trying to get the string built correctly.
+        if String === proxy && !proxy.strip.empty?
+          if proxy.match(/^.*:\/\//)
+           proxy = URI.parse(proxy.strip)
+          else
+           proxy = URI.parse("#{url.scheme}://#{proxy.strip}")
+          end 
+        end
+        
         no_proxy = Chef::Config[:no_proxy] || env['NO_PROXY'] || env['no_proxy']
         excludes = no_proxy.to_s.split(/\s*,\s*/).compact
         excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
diff --git a/lib/chef/http/decompressor.rb b/lib/chef/http/decompressor.rb
index e1d776d..a61f510 100644
--- a/lib/chef/http/decompressor.rb
+++ b/lib/chef/http/decompressor.rb
@@ -79,10 +79,10 @@ class Chef
         else
           case response[CONTENT_ENCODING]
           when GZIP
-            Chef::Log.debug "decompressing gzip response"
+            Chef::Log.debug "Decompressing gzip response"
             Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body)
           when DEFLATE
-            Chef::Log.debug "decompressing deflate response"
+            Chef::Log.debug "Decompressing deflate response"
             Zlib::Inflate.inflate(response.body)
           else
             response.body
diff --git a/lib/chef/http/http_request.rb b/lib/chef/http/http_request.rb
index 7582f44..1baf572 100644
--- a/lib/chef/http/http_request.rb
+++ b/lib/chef/http/http_request.rb
@@ -40,7 +40,7 @@ class Chef
 
       engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
 
-      UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +http://opscode.com)"
+      UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +https://chef.io)"
       DEFAULT_UA = "Chef Client" << UA_COMMON
 
       USER_AGENT = "User-Agent".freeze
diff --git a/lib/chef/http/json_input.rb b/lib/chef/http/json_input.rb
index 23ccc3a..3296d88 100644
--- a/lib/chef/http/json_input.rb
+++ b/lib/chef/http/json_input.rb
@@ -25,14 +25,19 @@ class Chef
     # Middleware that takes json input and turns it into raw text
     class JSONInput
 
+      attr_accessor :opts
+
       def initialize(opts={})
+        @opts = opts
       end
 
       def handle_request(method, url, headers={}, data=false)
         if data && should_encode_as_json?(headers)
           headers.delete_if { |key, _value| key.downcase == 'content-type' }
           headers["Content-Type"] = 'application/json'
-          data = Chef::JSONCompat.to_json(data)
+          json_opts = {}
+          json_opts[:validate_utf8] = opts[:validate_utf8] if opts.has_key?(:validate_utf8)
+          data = Chef::JSONCompat.to_json(data, json_opts)
           # Force encoding to binary to fix SSL related EOFErrors
           # cf. http://tickets.opscode.com/browse/CHEF-2363
           # http://redmine.ruby-lang.org/issues/5233
diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb
index d0b3b4c..5e9f29a 100644
--- a/lib/chef/json_compat.rb
+++ b/lib/chef/json_compat.rb
@@ -41,6 +41,7 @@ class Chef
     CHEF_RESOURCECOLLECTION = "Chef::ResourceCollection".freeze
     CHEF_RESOURCESET        = "Chef::ResourceCollection::ResourceSet".freeze
     CHEF_RESOURCELIST       = "Chef::ResourceCollection::ResourceList".freeze
+    CHEF_RUNLISTEXPANSION   = "Chef::RunListExpansion".freeze
 
     class <<self
 
diff --git a/lib/chef/key.rb b/lib/chef/key.rb
new file mode 100644
index 0000000..be4be7f
--- /dev/null
+++ b/lib/chef/key.rb
@@ -0,0 +1,271 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/json_compat'
+require 'chef/mixin/params_validate'
+require 'chef/exceptions'
+
+class Chef
+  # Class for interacting with a chef key object. Can be used to create new keys,
+  # save to server, load keys from server, list keys, delete keys, etc.
+  #
+  # @author Tyler Cloke
+  #
+  # @attr [String] actor           the name of the client or user that this key is for
+  # @attr [String] name            the name of the key
+  # @attr [String] public_key      the RSA string of this key
+  # @attr [String] private_key     the RSA string of the private key if returned via a POST or PUT
+  # @attr [String] expiration_date the ISO formatted string YYYY-MM-DDTHH:MM:SSZ, i.e. 2020-12-24T21:00:00Z
+  # @attr [String] rest            Chef::REST object, initialized and cached via chef_rest method
+  # @attr [string] api_base        either "users" or "clients", initialized and cached via api_base method
+  #
+  # @attr_reader [String] actor_field_name must be either 'client' or 'user'
+  class Key
+
+    include Chef::Mixin::ParamsValidate
+
+    attr_reader :actor_field_name
+
+    def initialize(actor, actor_field_name)
+      # Actor that the key is for, either a client or a user.
+      @actor = actor
+
+      unless actor_field_name == "user" || actor_field_name == "client"
+        raise Chef::Exceptions::InvalidKeyArgument, "the second argument to initialize must be either 'user' or 'client'"
+      end
+
+      @actor_field_name = actor_field_name
+
+      @name = nil
+      @public_key = nil
+      @private_key = nil
+      @expiration_date = nil
+      @create_key = nil
+    end
+
+    def chef_rest
+      @rest ||= if @actor_field_name == "user"
+                  Chef::REST.new(Chef::Config[:chef_server_root])
+                else
+                  Chef::REST.new(Chef::Config[:chef_server_url])
+                end
+    end
+
+    def api_base
+      @api_base ||= if @actor_field_name == "user"
+                      "users"
+                    else
+                      "clients"
+                    end
+    end
+
+    def actor(arg=nil)
+      set_or_return(:actor, arg,
+                    :regex => /^[a-z0-9\-_]+$/)
+    end
+
+    def name(arg=nil)
+      set_or_return(:name, arg,
+                    :kind_of => String)
+    end
+
+    def public_key(arg=nil)
+      raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set the public_key if create_key is true" if !arg.nil? && @create_key
+      set_or_return(:public_key, arg,
+                    :kind_of => String)
+    end
+
+    def private_key(arg=nil)
+      set_or_return(:private_key, arg,
+                    :kind_of => String)
+    end
+
+    def delete_public_key
+      @public_key = nil
+    end
+
+    def delete_create_key
+      @create_key = nil
+    end
+
+    def create_key(arg=nil)
+      raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set create_key to true if the public_key field exists" if arg == true && !@public_key.nil?
+      set_or_return(:create_key, arg,
+                    :kind_of => [TrueClass, FalseClass])
+    end
+
+    def expiration_date(arg=nil)
+      set_or_return(:expiration_date, arg,
+                    :regex => /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|infinity)$/)
+    end
+
+    def to_hash
+      result = {
+        @actor_field_name => @actor
+      }
+      result["name"] = @name if @name
+      result["public_key"] = @public_key if @public_key
+      result["private_key"] = @private_key if @private_key
+      result["expiration_date"] = @expiration_date if @expiration_date
+      result["create_key"] = @create_key if @create_key
+      result
+    end
+
+    def to_json(*a)
+      Chef::JSONCompat.to_json(to_hash, *a)
+    end
+
+    def create
+      # if public_key is undefined and create_key is false, we cannot create
+      if @public_key.nil? && !@create_key
+        raise Chef::Exceptions::MissingKeyAttribute, "either public_key must be defined or create_key must be true"
+      end
+
+      # defaults the key name to the fingerprint of the key
+      if @name.nil?
+        # if they didn't pass a public_key,
+        #then they must supply a name because we can't generate a fingerprint
+        unless @public_key.nil?
+          @name = fingerprint
+        else
+          raise Chef::Exceptions::MissingKeyAttribute, "a name cannot be auto-generated if no public key passed, either pass a public key or supply a name"
+        end
+      end
+
+      payload = {"name" => @name}
+      payload['public_key'] = @public_key unless @public_key.nil?
+      payload['create_key'] = @create_key if @create_key
+      payload['expiration_date'] = @expiration_date unless @expiration_date.nil?
+      result = chef_rest.post_rest("#{api_base}/#{@actor}/keys", payload)
+      # append the private key to the current key if the server returned one,
+      # since the POST endpoint just returns uri and private_key if needed.
+      new_key = self.to_hash
+      new_key["private_key"] = result["private_key"] if result["private_key"]
+      Chef::Key.from_hash(new_key)
+    end
+
+    def fingerprint
+      self.class.generate_fingerprint(@public_key)
+    end
+
+    # set @name and pass put_name if you wish to update the name of an existing key put_name to @name
+    def update(put_name=nil)
+      if @name.nil? && put_name.nil?
+        raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated or you must pass a name to update when update is called"
+      end
+
+      # If no name was passed, fall back to using @name in the PUT URL, otherwise
+      # use the put_name passed. This will update the a key by the name put_name
+      # to @name.
+      put_name = @name if put_name.nil?
+
+      new_key = chef_rest.put_rest("#{api_base}/#{@actor}/keys/#{put_name}", to_hash)
+      # if the server returned a public_key, remove the create_key field, as we now have a key
+      if new_key["public_key"]
+        self.delete_create_key
+      end
+      Chef::Key.from_hash(self.to_hash.merge(new_key))
+    end
+
+    def save
+      create
+    rescue Net::HTTPServerException => e
+      if e.response.code == "409"
+        update
+      else
+        raise e
+      end
+    end
+
+    def destroy
+      if @name.nil?
+        raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated when delete is called"
+      end
+
+      chef_rest.delete_rest("#{api_base}/#{@actor}/keys/#{@name}")
+    end
+
+    # Class methods
+    def self.from_hash(key_hash)
+      if key_hash.has_key?("user")
+        key = Chef::Key.new(key_hash["user"], "user")
+      elsif key_hash.has_key?("client")
+        key = Chef::Key.new(key_hash["client"], "client")
+      else
+        raise Chef::Exceptions::MissingKeyAttribute, "The hash passed to from_hash does not contain the key 'user' or 'client'. Please pass a hash that defines one of those keys."
+      end
+      key.name key_hash['name'] if key_hash.key?('name')
+      key.public_key key_hash['public_key'] if key_hash.key?('public_key')
+      key.private_key key_hash['private_key'] if key_hash.key?('private_key')
+      key.create_key key_hash['create_key'] if key_hash.key?('create_key')
+      key.expiration_date key_hash['expiration_date'] if key_hash.key?('expiration_date')
+      key
+    end
+
+    def self.from_json(json)
+      Chef::Key.from_hash(Chef::JSONCompat.from_json(json))
+    end
+
+    class << self
+      alias_method :json_create, :from_json
+    end
+
+    def self.list_by_user(actor, inflate=false)
+      keys = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("users/#{actor}/keys")
+      self.list(keys, actor, :load_by_user, inflate)
+    end
+
+    def self.list_by_client(actor, inflate=false)
+      keys = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{actor}/keys")
+      self.list(keys, actor, :load_by_client, inflate)
+    end
+
+    def self.load_by_user(actor, key_name)
+      response = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("users/#{actor}/keys/#{key_name}")
+      Chef::Key.from_hash(response.merge({"user" => actor}))
+    end
+
+    def self.load_by_client(actor, key_name)
+      response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{actor}/keys/#{key_name}")
+      Chef::Key.from_hash(response.merge({"client" => actor}))
+    end
+
+    def self.generate_fingerprint(public_key)
+        openssl_key_object = OpenSSL::PKey::RSA.new(public_key)
+        data_string = OpenSSL::ASN1::Sequence([
+                                                OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.n),
+                                                OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.e)
+                                              ])
+        OpenSSL::Digest::SHA1.hexdigest(data_string.to_der).scan(/../).join(':')
+    end
+
+    private
+
+    def self.list(keys, actor, load_method_symbol, inflate)
+      if inflate
+        keys.inject({}) do |key_map, result|
+          name = result["name"]
+          key_map[name] = Chef::Key.send(load_method_symbol, actor, name)
+          key_map
+        end
+      else
+        keys
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index 2e0694a..5df24fa 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -27,6 +27,7 @@ require 'chef/knife/core/subcommand_loader'
 require 'chef/knife/core/ui'
 require 'chef/local_mode'
 require 'chef/rest'
+require 'chef/http/authenticator'
 require 'pp'
 
 class Chef
@@ -86,6 +87,16 @@ class Chef
     def self.inherited(subclass)
       unless subclass.unnamed?
         subcommands[subclass.snake_case_name] = subclass
+        subcommand_files[subclass.snake_case_name] +=
+          if subclass.superclass.to_s == "Chef::ChefFS::Knife"
+            # ChefFS-based commands have a superclass that defines an
+            # inhereited method which calls super. This means that the
+            # top of the call stack is not the class definition for
+            # our subcommand.  Try the second entry in the call stack.
+            [path_from_caller(caller[1])]
+          else
+            [path_from_caller(caller[0])]
+          end
       end
     end
 
@@ -120,17 +131,29 @@ class Chef
     end
 
     def self.subcommand_loader
-      @subcommand_loader ||= Knife::SubcommandLoader.new(chef_config_dir)
+      @subcommand_loader ||= Chef::Knife::SubcommandLoader.for_config(chef_config_dir)
     end
 
     def self.load_commands
       @commands_loaded ||= subcommand_loader.load_commands
     end
 
+    def self.guess_category(args)
+      subcommand_loader.guess_category(args)
+    end
+
+    def self.subcommand_class_from(args)
+      subcommand_loader.command_class_from(args) || subcommand_not_found!(args)
+    end
+
     def self.subcommands
       @@subcommands ||= {}
     end
 
+    def self.subcommand_files
+      @@subcommand_files ||= Hash.new([])
+    end
+
     def self.subcommands_by_category
       unless @subcommands_by_category
         @subcommands_by_category = Hash.new { |hash, key| hash[key] = [] }
@@ -141,30 +164,6 @@ class Chef
       @subcommands_by_category
     end
 
-    # Print the list of subcommands knife knows about. If +preferred_category+
-    # is given, only subcommands in that category are shown
-    def self.list_commands(preferred_category=nil)
-      load_commands
-
-      category_desc = preferred_category ? preferred_category + " " : ''
-      msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
-
-      if preferred_category && subcommands_by_category.key?(preferred_category)
-        commands_to_show = {preferred_category => subcommands_by_category[preferred_category]}
-      else
-        commands_to_show = subcommands_by_category
-      end
-
-      commands_to_show.sort.each do |category, commands|
-        next if category =~ /deprecated/i
-        msg "** #{category.upcase} COMMANDS **"
-        commands.sort.each do |command|
-          msg subcommands[command].banner if subcommands[command]
-        end
-        msg
-      end
-    end
-
     # Shared with subclasses
     @@chef_config_dir = nil
 
@@ -205,7 +204,6 @@ class Chef
         Chef::Log.level(:debug)
       end
 
-      load_commands
       subcommand_class = subcommand_class_from(args)
       subcommand_class.options = options.merge!(subcommand_class.options)
       subcommand_class.load_deps
@@ -214,34 +212,6 @@ class Chef
       instance.run_with_pretty_exceptions
     end
 
-    def self.guess_category(args)
-      category_words = args.select {|arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
-      category_words.map! {|w| w.split('-')}.flatten!
-      matching_category = nil
-      while (!matching_category) && (!category_words.empty?)
-        candidate_category = category_words.join(' ')
-        matching_category = candidate_category if subcommands_by_category.key?(candidate_category)
-        matching_category || category_words.pop
-      end
-      matching_category
-    end
-
-    def self.subcommand_class_from(args)
-      command_words = args.select {|arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
-
-      subcommand_class = nil
-
-      while ( !subcommand_class ) && ( !command_words.empty? )
-        snake_case_class_name = command_words.join("_")
-        unless subcommand_class = subcommands[snake_case_class_name]
-          command_words.pop
-        end
-      end
-      # see if we got the command as e.g., knife node-list
-      subcommand_class ||= subcommands[args.first.gsub('-', '_')]
-      subcommand_class || subcommand_not_found!(args)
-    end
-
     def self.dependency_loaders
       @dependency_loaders ||= []
     end
@@ -260,11 +230,21 @@ class Chef
 
     OFFICIAL_PLUGINS = %w[ec2 rackspace windows openstack terremark bluebox]
 
+    def self.path_from_caller(caller_line)
+      caller_line.split(/:\d+/).first
+    end
+
     # :nodoc:
     # Error out and print usage. probably because the arguments given by the
     # user could not be resolved to a subcommand.
     def self.subcommand_not_found!(args)
-      ui.fatal("Cannot find sub command for: '#{args.join(' ')}'")
+      ui.fatal("Cannot find subcommand for: '#{args.join(' ')}'")
+
+      # Mention rehash when the subcommands cache(plugin_manifest.json) is used
+      if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader) ||
+         subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::CustomManifestLoader)
+        ui.info("If this is a recently installed plugin, please run 'knife rehash' to update the subcommands cache.")
+      end
 
       if category_commands = guess_category(args)
         list_commands(category_commands)
@@ -279,6 +259,20 @@ class Chef
       exit 10
     end
 
+    def self.list_commands(preferred_category=nil)
+      category_desc = preferred_category ? preferred_category + " " : ''
+      msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
+      subcommand_loader.list_commands(preferred_category).sort.each do |category, commands|
+        next if category =~ /deprecated/i
+        msg "** #{category.upcase} COMMANDS **"
+        commands.sort.each do |command|
+          subcommand_loader.load_command(command)
+          msg subcommands[command].banner if subcommands[command]
+        end
+        msg
+      end
+    end
+
     def self.reset_config_path!
       @@chef_config_dir = nil
     end
@@ -312,7 +306,7 @@ class Chef
 
       # copy Mixlib::CLI over so that it can be configured in knife.rb
       # config file
-      Chef::Config[:verbosity] = config[:verbosity]
+      Chef::Config[:verbosity] = config[:verbosity] if config[:verbosity]
     end
 
     def parse_options(args)
@@ -358,7 +352,7 @@ class Chef
 
       case Chef::Config[:verbosity]
       when 0, nil
-        Chef::Config[:log_level] = :error
+        Chef::Config[:log_level] = :warn
       when 1
         Chef::Config[:log_level] = :info
       else
@@ -400,11 +394,14 @@ class Chef
     end
 
     def configure_chef
+      # knife needs to send logger output to STDERR by default
+      Chef::Config[:log_location] = STDERR
       config_loader = self.class.load_config(config[:config_file])
       config[:config_file] = config_loader.config_location
 
       merge_configs
       apply_computed_config
+      Chef::Config.export_proxies
       # This has to be after apply_computed_config so that Mixlib::Log is configured
       Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file]
     end
@@ -483,6 +480,15 @@ class Chef
       when Net::HTTPServiceUnavailable
         ui.error "Service temporarily unavailable"
         ui.info "Response: #{format_rest_error(response)}"
+      when Net::HTTPNotAcceptable
+        version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
+        client_api_version = version_header["request_version"]
+        min_server_version = version_header["min_version"]
+        max_server_version = version_header["max_version"]
+        ui.error "The version of Chef that Knife is using is not supported by the Chef server you sent this request to"
+        ui.info "The request that Knife sent was using API version #{client_api_version}"
+        ui.info "The Chef server you sent the request to supports a min API verson of #{min_server_version} and a max API version of #{max_server_version}"
+        ui.info "Please either update your Chef client or server to be a compatible set"
       else
         ui.error response.message
         ui.info "Response: #{format_rest_error(response)}"
@@ -539,6 +545,16 @@ class Chef
       self.msg("Deleted #{obj_name}")
     end
 
+    # helper method for testing if a field exists
+    # and returning the usage and proper error if not
+    def test_mandatory_field(field, fieldname)
+      if field.nil?
+        show_usage
+        ui.fatal("You must specify a #{fieldname}")
+        exit 1
+      end
+    end
+
     def rest
       @rest ||= begin
         require 'chef/rest'
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index a4095e8..30ea3ca 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -42,7 +42,7 @@ class Chef
         Chef::Knife::Ssh.load_deps
       end
 
-      banner "knife bootstrap FQDN (options)"
+      banner "knife bootstrap [SSH_USER@]FQDN (options)"
 
       option :ssh_user,
         :short => "-x USERNAME",
@@ -74,8 +74,12 @@ class Chef
         :boolean => true
 
       option :identity_file,
-        :short => "-i IDENTITY_FILE",
         :long => "--identity-file IDENTITY_FILE",
+        :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead."
+
+      option :ssh_identity_file,
+        :short => "-i IDENTITY_FILE",
+        :long => "--ssh-identity-file IDENTITY_FILE",
         :description => "The SSH identity file used for authentication"
 
       option :chef_node_name,
@@ -122,6 +126,11 @@ class Chef
         :description => "Execute the bootstrap via sudo",
         :boolean => true
 
+      option :preserve_home,
+        :long => "--sudo-preserve-home",
+        :description => "Preserve non-root user HOME environment variable with sudo",
+        :boolean => true
+
       option :use_sudo_password,
         :long => "--use-sudo-password",
         :description => "Execute the bootstrap via sudo with password",
@@ -143,12 +152,34 @@ class Chef
         :proc => lambda { |o| o.split(/[\s,]+/) },
         :default => []
 
+      option :policy_name,
+        :long         => "--policy-name POLICY_NAME",
+        :description  => "Policyfile name to use (--policy-group must also be given)",
+        :default      => nil
+
+      option :policy_group,
+        :long         => "--policy-group POLICY_GROUP",
+        :description  => "Policy group name to use (--policy-name must also be given)",
+        :default      => nil
+
+      option :tags,
+        :long => "--tags TAGS",
+        :description => "Comma separated list of tags to apply to the node",
+        :proc => lambda { |o| o.split(/[\s,]+/) },
+        :default => []
+
       option :first_boot_attributes,
         :short => "-j JSON_ATTRIBS",
         :long => "--json-attributes",
         :description => "A JSON string to be added to the first run of chef-client",
         :proc => lambda { |o| Chef::JSONCompat.parse(o) },
-        :default => {}
+        :default => nil
+
+      option :first_boot_attributes_from_file,
+        :long => "--json-attribute-file FILE",
+        :description => "A JSON file to be used to the first run of chef-client",
+        :proc => lambda { |o| Chef::JSONCompat.parse(File.read(o)) },
+        :default => nil
 
       option :host_key_verify,
         :long => "--[no-]host-key-verify",
@@ -240,13 +271,25 @@ class Chef
         "chef-full"
       end
 
+      def host_descriptor
+        Array(@name_args).first
+      end
+
       # The server_name is the DNS or IP we are going to connect to, it is not necessarily
       # the node name, the fqdn, or the hostname of the server.  This is a public API hook
       # which knife plugins use or inherit and override.
       #
       # @return [String] The DNS or IP that bootstrap will connect to
       def server_name
-        Array(@name_args).first
+        if host_descriptor
+          @server_name ||= host_descriptor.split('@').reverse[0]
+        end
+      end
+
+      def user_name
+        if host_descriptor
+          @user_name ||= host_descriptor.split('@').reverse[1]
+        end
       end
 
       def bootstrap_template
@@ -301,14 +344,24 @@ class Chef
         )
       end
 
+      def first_boot_attributes
+        @config[:first_boot_attributes] || @config[:first_boot_attributes_from_file] || {}
+      end
+
       def render_template
+        @config[:first_boot_attributes] = first_boot_attributes
         template_file = find_template
         template = IO.read(template_file).chomp
         Erubis::Eruby.new(template).evaluate(bootstrap_context)
       end
 
       def run
+        if @config[:first_boot_attributes] && @config[:first_boot_attributes_from_file]
+          raise Chef::Exceptions::BootstrapCommandInputError
+        end
+
         validate_name_args!
+        validate_options!
 
         $stdout.sync = true
 
@@ -316,6 +369,12 @@ class Chef
         # new client-side hawtness, just delete your validation key.
         if chef_vault_handler.doing_chef_vault? || 
             (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))
+
+          unless config[:chef_node_name]
+            ui.error("You must pass a node name with -N when bootstrapping with user credentials")
+            exit 1
+          end
+
           client_builder.run
 
           chef_vault_handler.run(node_name: config[:chef_node_name])
@@ -335,7 +394,7 @@ class Chef
           if config[:ssh_password]
             raise
           else
-            ui.info("Failed to authenticate #{config[:ssh_user]} - trying password auth")
+            ui.info("Failed to authenticate #{knife_ssh.config[:ssh_user]} - trying password auth")
             knife_ssh_with_password_auth.run
           end
         end
@@ -351,16 +410,27 @@ class Chef
         end
       end
 
+      def validate_options!
+        if incomplete_policyfile_options?
+          ui.error("--policy-name and --policy-group must be specified together")
+          exit 1
+        elsif policyfile_and_run_list_given?
+          ui.error("Policyfile options and --run-list are exclusive")
+          exit 1
+        end
+        true
+      end
+
       def knife_ssh
         ssh = Chef::Knife::Ssh.new
         ssh.ui = ui
         ssh.name_args = [ server_name, ssh_command ]
-        ssh.config[:ssh_user] = config[:ssh_user]
+        ssh.config[:ssh_user] = user_name || config[:ssh_user]
         ssh.config[:ssh_password] = config[:ssh_password]
         ssh.config[:ssh_port] = config[:ssh_port]
         ssh.config[:ssh_gateway] = config[:ssh_gateway]
         ssh.config[:forward_agent] = config[:forward_agent]
-        ssh.config[:identity_file] = config[:identity_file]
+        ssh.config[:ssh_identity_file] = config[:ssh_identity_file] || config[:identity_file]
         ssh.config[:manual] = true
         ssh.config[:host_key_verify] = config[:host_key_verify]
         ssh.config[:on_error] = :raise
@@ -369,7 +439,7 @@ class Chef
 
       def knife_ssh_with_password_auth
         ssh = knife_ssh
-        ssh.config[:identity_file] = nil
+        ssh.config[:ssh_identity_file] = nil
         ssh.config[:ssh_password] = ssh.get_password
         ssh
       end
@@ -378,11 +448,33 @@ class Chef
         command = render_template
 
         if config[:use_sudo]
-          command = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -SH #{command}" : "sudo -H #{command}"
+          sudo_prefix = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S " : "sudo "
+          command = config[:preserve_home] ? "#{sudo_prefix} #{command}" : "#{sudo_prefix} -H #{command}" 
         end
 
         command
       end
+
+      private
+
+      # True if policy_name and run_list are both given
+      def policyfile_and_run_list_given?
+        run_list_given? && policyfile_options_given?
+      end
+
+      def run_list_given?
+        !config[:run_list].nil? && !config[:run_list].empty?
+      end
+
+      def policyfile_options_given?
+        !!config[:policy_name]
+      end
+
+      # True if one of policy_name or policy_group was given, but not both
+      def incomplete_policyfile_options?
+        (!!config[:policy_name] ^ config[:policy_group])
+      end
+
     end
   end
 end
diff --git a/lib/chef/knife/bootstrap/chef_vault_handler.rb b/lib/chef/knife/bootstrap/chef_vault_handler.rb
index 749f61e..f658957 100644
--- a/lib/chef/knife/bootstrap/chef_vault_handler.rb
+++ b/lib/chef/knife/bootstrap/chef_vault_handler.rb
@@ -15,6 +15,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+require 'chef/knife/bootstrap'
 
 class Chef
   class Knife
diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb
index b9c1d98..7eb1e22 100644
--- a/lib/chef/knife/bootstrap/client_builder.rb
+++ b/lib/chef/knife/bootstrap/client_builder.rb
@@ -20,6 +20,7 @@ require 'chef/node'
 require 'chef/rest'
 require 'chef/api_client/registration'
 require 'chef/api_client'
+require 'chef/knife/bootstrap'
 require 'tmpdir'
 
 class Chef
@@ -90,6 +91,16 @@ class Chef
           knife_config[:run_list]
         end
 
+        # @return [String] policy_name from the knife_config
+        def policy_name
+          knife_config[:policy_name]
+        end
+
+        # @return [String] policy_group from the knife_config
+        def policy_group
+          knife_config[:policy_group]
+        end
+
         # @return [Hash,Array] Object representation of json first-boot attributes from the knife_config
         def first_boot_attributes
           knife_config[:first_boot_attributes]
@@ -140,6 +151,11 @@ class Chef
               node.run_list(normalized_run_list)
               node.normal_attrs = first_boot_attributes if first_boot_attributes
               node.environment(environment) if environment
+              node.policy_name = policy_name if policy_name
+              node.policy_group = policy_group if policy_group
+              (knife_config[:tags] || []).each do |tag|
+                node.tags << tag
+              end
               node
             end
         end
diff --git a/lib/chef/knife/bootstrap/templates/README.md b/lib/chef/knife/bootstrap/templates/README.md
index 13a0fe7..b5bca25 100644
--- a/lib/chef/knife/bootstrap/templates/README.md
+++ b/lib/chef/knife/bootstrap/templates/README.md
@@ -1,12 +1,11 @@
 This directory contains bootstrap templates which can be used with the -d flag
 to 'knife bootstrap' to install Chef in different ways. To simplify installation,
 and reduce the matrix of common installation patterns to support, we have
-standardized on the [Omnibus](https://github.com/opscode/omnibus-ruby) built installation 
+standardized on the [Omnibus](https://github.com/chef/omnibus) built installation
 packages.
 
 The 'chef-full' template downloads a script which is used to determine the correct
-Omnibus package for this system from the [Omnitruck](https://github.com/opscode/opscode-omnitruck) API. All other templates in this directory are deprecated and will be removed
-in the future.
+Omnibus package for this system from the [Omnitruck](https://docs.chef.io/api_omnitruck.html) API.
 
 You can still utilize custom bootstrap templates on your system if your installation
-needs are unique. Additional information can be found on the [docs site](http://docs.opscode.com/knife_bootstrap.html#custom-templates).
\ No newline at end of file
+needs are unique. Additional information can be found on the [docs site](https://docs.chef.io/knife_bootstrap.html#custom-templates).
diff --git a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb b/lib/chef/knife/bootstrap/templates/archlinux-gems.erb
deleted file mode 100644
index 55d2c0c..0000000
--- a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb
+++ /dev/null
@@ -1,76 +0,0 @@
-bash -c '
-<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-
-if [ ! -f /usr/bin/chef-client ]; then
-  pacman -Syy
-  pacman -S --noconfirm ruby ntp base-devel
-  ntpdate -u pool.ntp.org
-  gem install ohai --no-user-install --no-document --verbose
-  gem install chef --no-user-install --no-document --verbose <%= Chef::VERSION %>
-fi
-
-mkdir -p /etc/chef
-
-<% if validation_key -%>
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-<% end -%>
-
-<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
-<%= encrypted_data_bag_secret %>
-EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
-<% end -%>
-
-<% unless trusted_certs.empty? -%>
-mkdir -p /etc/chef/trusted_certs
-<%= trusted_certs %>
-<% end -%>
-
-<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
-mkdir -p /etc/chef/ohai/hints
-
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= Chef::JSONCompat.to_json(hash) %>
-EOP
-<% end -%>
-<% end -%>
-
-<% if client_pem -%>
-cat > /etc/chef/client.pem <<'EOP'
-<%= ::File.read(::File.expand_path(client_pem)) %>
-EOP
-chmod 0600 /etc/chef/client.pem
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-log_level        :info
-log_location     STDOUT
-chef_server_url  "<%= @chef_config[:chef_server_url] %>"
-validation_client_name "<%= @chef_config[:validation_client_name] %>"
-<% if @config[:chef_node_name] -%>
-node_name "<%= @config[:chef_node_name] %>"
-<% else -%>
-# Using default node name (fqdn)
-<% end -%>
-# ArchLinux follows the Filesystem Hierarchy Standard
-file_cache_path    "/var/cache/chef"
-file_backup_path   "/var/lib/chef/backup"
-pid_file           "/var/run/chef/client.pid"
-cache_options({ :path => "/var/cache/chef/checksums", :skip_expires => true})
-<% if knife_config[:bootstrap_proxy] %>
-http_proxy         "<%= knife_config[:bootstrap_proxy] %>"
-https_proxy        "<%= knife_config[:bootstrap_proxy] %>"
-<% end -%>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= Chef::JSONCompat.to_json(first_boot) %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/templates/chef-aix.erb b/lib/chef/knife/bootstrap/templates/chef-aix.erb
deleted file mode 100644
index 45fbba7..0000000
--- a/lib/chef/knife/bootstrap/templates/chef-aix.erb
+++ /dev/null
@@ -1,72 +0,0 @@
-ksh -c '
-
-function exists {
-  if type $1 >/dev/null 2>&1
-  then
-    return 0
-  else
-    return 1
-  fi
-}
-
-if ! exists /usr/bin/chef-client; then
-  <% if @chef_config[:aix_package] -%>
-    # Read the download URL/location from knife.rb with option aix_package
-    rm -rf /tmp/chef_installer # ensure there no older pkg
-    echo "<%= @chef_config[:aix_package] %>"
-    perl -e '\''use LWP::Simple; getprint($ARGV[0]);'\'' <%= @chef_config[:aix_package] %> > /tmp/chef_installer
-    installp -aYF -d  /tmp/chef_installer chef
-  <% else -%>
-     echo ":aix_package location is not set in knife.rb"
-     exit
-  <% end -%>
-fi
-
-mkdir -p /etc/chef
-
-<% if client_pem -%>
-cat > /etc/chef/client.pem <<'EOP'
-<%= ::File.read(::File.expand_path(client_pem)) %>
-EOP
-chmod 0600 /etc/chef/client.pem
-<% end -%>
-
-<% if validation_key -%>
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-<% end -%>
-
-<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
-<%= encrypted_data_bag_secret %>
-EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
-<% end -%>
-
-<% unless trusted_certs.empty? -%>
-mkdir -p /etc/chef/trusted_certs
-<%= trusted_certs %>
-<% end -%>
-
-<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
-mkdir -p /etc/chef/ohai/hints
-
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= Chef::JSONCompat.to_json(hash) %>
-EOP
-<% end -%>
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-<%= config_content %>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= Chef::JSONCompat.to_json(first_boot) %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb
index a87ab8e..575aec0 100644
--- a/lib/chef/knife/bootstrap/templates/chef-full.erb
+++ b/lib/chef/knife/bootstrap/templates/chef-full.erb
@@ -1,17 +1,18 @@
-bash -c '
+sh -c '
 <%= "export https_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
 
-distro=`uname -s`
-
-if test "x$distro" = "xSunOS"; then
-  if test -d "/usr/sfw/bin"; then
-    PATH=/usr/sfw/bin:$PATH
-    export PATH
-  fi
+if test "x$TMPDIR" = "x"; then
+  tmp="/tmp"
+else
+  tmp=$TMPDIR
 fi
 
+# secure-ish temp dir creation without having mktemp available (DDoS-able but not exploitable)
+tmp_dir="$tmp/install.sh.$$"
+(umask 077 && mkdir $tmp_dir) || exit 1
+
 exists() {
-  if command -v $1 &>/dev/null
+  if command -v $1 >/dev/null 2>&1
   then
     return 0
   else
@@ -19,41 +20,183 @@ exists() {
   fi
 }
 
+http_404_error() {
+  echo "ERROR 404: Could not retrieve a valid install.sh!"
+  exit 1
+}
+
+capture_tmp_stderr() {
+  # spool up /tmp/stderr from all the commands we called
+  if test -f "$tmp_dir/stderr"; then
+    output=`cat $tmp_dir/stderr`
+    stderr_results="${stderr_results}\nSTDERR from $1:\n\n$output\n"
+    rm $tmp_dir/stderr
+  fi
+}
+
+# do_wget URL FILENAME
+do_wget() {
+  echo "trying wget..."
+  wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> -O "$2" "$1" 2>$tmp_dir/stderr
+  rc=$?
+  # check for 404
+  grep "ERROR 404" $tmp_dir/stderr 2>&1 >/dev/null
+  if test $? -eq 0; then
+    http_404_error
+  fi
+
+  # check for bad return status or empty output
+  if test $rc -ne 0 || test ! -s "$2"; then
+    capture_tmp_stderr "wget"
+    return 1
+  fi
+
+  return 0
+}
+
+# do_curl URL FILENAME
+do_curl() {
+  echo "trying curl..."
+  curl -sL <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> -D $tmp_dir/stderr -o "$2" "$1" 2>$tmp_dir/stderr
+  rc=$?
+  # check for 404
+  grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+  if test $? -eq 0; then
+    http_404_error
+  fi
+
+  # check for bad return status or empty output
+  if test $rc -ne 0 || test ! -s "$2"; then
+    capture_tmp_stderr "curl"
+    return 1
+  fi
+
+  return 0
+}
+
+# do_fetch URL FILENAME
+do_fetch() {
+  echo "trying fetch..."
+  fetch -o "$2" "$1" 2>$tmp_dir/stderr
+  # check for bad return status
+  test $? -ne 0 && return 1
+  return 0
+}
+
+# do_perl URL FILENAME
+do_perl() {
+  echo "trying perl..."
+  perl -e "use LWP::Simple; getprint(shift @ARGV);" "$1" > "$2" 2>$tmp_dir/stderr
+  rc=$?
+  # check for 404
+  grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+  if test $? -eq 0; then
+    http_404_error
+  fi
+
+  # check for bad return status or empty output
+  if test $rc -ne 0 || test ! -s "$2"; then
+    capture_tmp_stderr "perl"
+    return 1
+  fi
+
+  return 0
+}
+
+# do_python URL FILENAME
+do_python() {
+  echo "trying python..."
+  python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2" 2>$tmp_dir/stderr
+  rc=$?
+  # check for 404
+  grep "HTTP Error 404" $tmp_dir/stderr 2>&1 >/dev/null
+  if test $? -eq 0; then
+    http_404_error
+  fi
+
+  # check for bad return status or empty output
+  if test $rc -ne 0 || test ! -s "$2"; then
+    capture_tmp_stderr "python"
+    return 1
+  fi
+  return 0
+}
+
+# do_download URL FILENAME
+do_download() {
+  PATH=/opt/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sfw/bin:/sbin:/bin:/usr/sbin:/usr/bin
+  export PATH
+
+  echo "downloading $1"
+  echo "  to file $2"
+
+  # we try all of these until we get success.
+  # perl, in particular may be present but LWP::Simple may not be installed
+
+  if exists wget; then
+    do_wget $1 $2 && return 0
+  fi
+
+  if exists curl; then
+    do_curl $1 $2 && return 0
+  fi
+
+  if exists fetch; then
+    do_fetch $1 $2 && return 0
+  fi
+
+  if exists perl; then
+    do_perl $1 $2 && return 0
+  fi
+
+  if exists python; then
+    do_python $1 $2 && return 0
+  fi
+
+  echo ">>>>>> wget, curl, fetch, perl, or python not found on this instance."
+
+  if test "x$stderr_results" != "x"; then
+    echo "\nDEBUG OUTPUT FOLLOWS:\n$stderr_results"
+  fi
+
+  return 16
+}
+
 <% if knife_config[:bootstrap_install_command] %>
   <%= knife_config[:bootstrap_install_command] %>
 <% else %>
   install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.opscode.com/chef/install.sh" %>"
-  if ! exists /usr/bin/chef-client; then
-    echo "Installing Chef Client..."
-    if exists wget; then
-      bash <(wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> ${install_sh} -O -) <%= latest_current_chef_version_string %>
-    elif exists curl; then
-      bash <(curl -L <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> ${install_sh}) <%= latest_current_chef_version_string %>
-    else
-      echo "Neither wget nor curl found. Please install one and try again." >&2
-      exit 1
-    fi
+  if test -f /usr/bin/chef-client; then
+    echo "-----> Existing Chef installation detected"
+  else
+    echo "-----> Installing Chef Omnibus (<%= latest_current_chef_version_string %>)"
+    do_download ${install_sh} $tmp_dir/install.sh
+    sh $tmp_dir/install.sh -P chef <%= latest_current_chef_version_string %>
   fi
 <% end %>
 
+if test "x$tmp_dir" != "x"; then
+  rm -r "$tmp_dir"
+fi
+
 mkdir -p /etc/chef
 
 <% if client_pem -%>
-cat > /etc/chef/client.pem <<'EOP'
+cat > /etc/chef/client.pem <<EOP
 <%= ::File.read(::File.expand_path(client_pem)) %>
 EOP
 chmod 0600 /etc/chef/client.pem
 <% end -%>
 
 <% if validation_key -%>
-cat > /etc/chef/validation.pem <<'EOP'
+cat > /etc/chef/validation.pem <<EOP
 <%= validation_key %>
 EOP
 chmod 0600 /etc/chef/validation.pem
 <% end -%>
 
 <% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
+cat > /etc/chef/encrypted_data_bag_secret <<EOP
 <%= encrypted_data_bag_secret %>
 EOP
 chmod 0600 /etc/chef/encrypted_data_bag_secret
@@ -69,20 +212,20 @@ mkdir -p /etc/chef/trusted_certs
 mkdir -p /etc/chef/ohai/hints
 
 <% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+cat > /etc/chef/ohai/hints/<%= name %>.json <<EOP
 <%= Chef::JSONCompat.to_json(hash) %>
 EOP
 <% end -%>
 <% end -%>
 
-cat > /etc/chef/client.rb <<'EOP'
+cat > /etc/chef/client.rb <<EOP
 <%= config_content %>
 EOP
 
-cat > /etc/chef/first-boot.json <<'EOP'
+cat > /etc/chef/first-boot.json <<EOP
 <%= Chef::JSONCompat.to_json(first_boot) %>
 EOP
 
-echo "Starting first Chef Client run..."
+echo "Starting the first Chef Client run..."
 
 <%= start_chef %>'
diff --git a/lib/chef/knife/client_bulk_delete.rb b/lib/chef/knife/client_bulk_delete.rb
index f2be772..b439e6f 100644
--- a/lib/chef/knife/client_bulk_delete.rb
+++ b/lib/chef/knife/client_bulk_delete.rb
@@ -23,7 +23,7 @@ class Chef
     class ClientBulkDelete < Knife
 
       deps do
-        require 'chef/api_client'
+        require 'chef/api_client_v1'
         require 'chef/json_compat'
       end
 
@@ -39,7 +39,7 @@ class Chef
           ui.fatal("You must supply a regular expression to match the results against")
           exit 42
         end
-        all_clients = Chef::ApiClient.list(true)
+        all_clients = Chef::ApiClientV1.list(true)
 
         matcher = /#{name_args[0]}/
         clients_to_delete = {}
diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb
index 477a400..fa9a1a7 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -23,63 +23,87 @@ class Chef
     class ClientCreate < Knife
 
       deps do
-        require 'chef/api_client'
+        require 'chef/api_client_v1'
         require 'chef/json_compat'
       end
 
       option :file,
-        :short => "-f FILE",
-        :long  => "--file FILE",
-        :description => "Write the key to a file"
+             :short => "-f FILE",
+             :long  => "--file FILE",
+             :description => "Write the private key to a file if the server generated one."
 
       option :admin,
-        :short => "-a",
-        :long  => "--admin",
-        :description => "Create the client as an admin",
-        :boolean => true
+             :short => "-a",
+             :long  => "--admin",
+             :description => "Open Source Chef 11 only. Create the client as an admin.",
+             :boolean => true
 
       option :validator,
-        :long  => "--validator",
-        :description => "Create the client as a validator",
-        :boolean => true
+             :long  => "--validator",
+             :description => "Create the client as a validator.",
+             :boolean => true
 
-      banner "knife client create CLIENT (options)"
+      option :public_key,
+             :short => "-p FILE",
+             :long  => "--public-key",
+             :description => "Set the initial default key for the client from a file on disk (cannot pass with --prevent-keygen)."
+
+      option :prevent_keygen,
+             :short => "-k",
+             :long  => "--prevent-keygen",
+             :description => "API V1 only. Prevent server from generating a default key pair for you. Cannot be passed with --public-key.",
+             :boolean => true
+
+      banner "knife client create CLIENTNAME (options)"
+
+      def client
+        @client_field ||= Chef::ApiClientV1.new
+      end
+
+      def create_client(client)
+        # should not be using save :( bad behavior
+        Chef::ApiClientV1.from_hash(client).save
+      end
 
       def run
-        @client_name = @name_args[0]
+        test_mandatory_field(@name_args[0], "client name")
+        client.name @name_args[0]
 
-        if @client_name.nil?
+        if config[:public_key] && config[:prevent_keygen]
           show_usage
-          ui.fatal("You must specify a client name")
+          ui.fatal("You cannot pass --public-key and --prevent-keygen")
           exit 1
         end
 
-        client_hash = {
-          "name" => @client_name,
-          "admin" => !!config[:admin],
-          "validator" => !!config[:validator]
-        }
+        if !config[:prevent_keygen] && !config[:public_key]
+          client.create_key(true)
+        end
+
+        if config[:admin]
+          client.admin(true)
+        end
 
-        output = Chef::ApiClient.from_hash(edit_hash(client_hash))
+        if config[:validator]
+          client.validator(true)
+        end
 
-        # Chef::ApiClient.save will try to create a client and if it
-        # exists will update it instead silently.
-        client = output.save
+        if config[:public_key]
+          client.public_key File.read(File.expand_path(config[:public_key]))
+        end
 
-        # We only get a private_key on client creation, not on client update.
-        if client['private_key']
-          ui.info("Created #{output}")
+        output = edit_data(client)
+        final_client = create_client(output)
+        ui.info("Created #{final_client}")
 
+        # output private_key if one
+        if final_client.private_key
           if config[:file]
             File.open(config[:file], "w") do |f|
-              f.print(client['private_key'])
+              f.print(final_client.private_key)
             end
           else
-            puts client['private_key']
+            puts final_client.private_key
           end
-        else
-          ui.error "Client '#{client['name']}' already exists"
-          exit 1
         end
       end
     end
diff --git a/lib/chef/knife/client_delete.rb b/lib/chef/knife/client_delete.rb
index d7d302e..a49c086 100644
--- a/lib/chef/knife/client_delete.rb
+++ b/lib/chef/knife/client_delete.rb
@@ -23,7 +23,7 @@ class Chef
     class ClientDelete < Knife
 
       deps do
-        require 'chef/api_client'
+        require 'chef/api_client_v1'
         require 'chef/json_compat'
       end
 
@@ -43,8 +43,8 @@ class Chef
           exit 1
         end
 
-        delete_object(Chef::ApiClient, @client_name, 'client') {
-          object = Chef::ApiClient.load(@client_name)
+        delete_object(Chef::ApiClientV1, @client_name, 'client') {
+          object = Chef::ApiClientV1.load(@client_name)
           if object.validator
             unless config[:delete_validators]
               ui.fatal("You must specify --delete-validators to delete the validator client #{@client_name}")
diff --git a/lib/chef/knife/client_edit.rb b/lib/chef/knife/client_edit.rb
index c81bce9..5dcd8f2 100644
--- a/lib/chef/knife/client_edit.rb
+++ b/lib/chef/knife/client_edit.rb
@@ -23,7 +23,7 @@ class Chef
     class ClientEdit < Knife
 
       deps do
-        require 'chef/api_client'
+        require 'chef/api_client_v1'
         require 'chef/json_compat'
       end
 
@@ -38,7 +38,15 @@ class Chef
           exit 1
         end
 
-        edit_object(Chef::ApiClient, @client_name)
+        original_data = Chef::ApiClientV1.load(@client_name).to_hash
+        edited_client = edit_data(original_data)
+        if original_data != edited_client
+          client = Chef::ApiClientV1.from_hash(edited_client)
+          client.save
+          ui.msg("Saved #{client}.")
+        else
+          ui.msg("Client unchanged, not saving.")
+        end
       end
     end
   end
diff --git a/lib/chef/knife/client_key_create.rb b/lib/chef/knife/client_key_create.rb
new file mode 100644
index 0000000..3b7e97e
--- /dev/null
+++ b/lib/chef/knife/client_key_create.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+require 'chef/knife/key_create_base'
+
+class Chef
+  class Knife
+    # Implements knife user key create using Chef::Knife::KeyCreate
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the client that this key is for
+    class ClientKeyCreate < Knife
+      include Chef::Knife::KeyCreateBase
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def actor_field_name
+        'client'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyCreate.new(@actor, actor_field_name, ui, config)
+      end
+
+      def actor_missing_error
+        'You must specify a client name'
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/client_key_delete.rb b/lib/chef/knife/client_key_delete.rb
new file mode 100644
index 0000000..8ecdfe1
--- /dev/null
+++ b/lib/chef/knife/client_key_delete.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+
+class Chef
+  class Knife
+    # Implements knife client key delete using Chef::Knife::KeyDelete
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the client that this key is for
+    class ClientKeyDelete < Knife
+      banner "knife client key delete CLIENT KEYNAME (options)"
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def actor_field_name
+        'client'
+      end
+
+      def actor_missing_error
+        'You must specify a client name'
+      end
+
+      def keyname_missing_error
+        'You must specify a key name'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyDelete.new(@name, @actor, actor_field_name, ui)
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+        @name = params[1]
+        if @name.nil?
+          show_usage
+          ui.fatal(keyname_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/client_key_edit.rb b/lib/chef/knife/client_key_edit.rb
new file mode 100644
index 0000000..1de45f4
--- /dev/null
+++ b/lib/chef/knife/client_key_edit.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+require 'chef/knife/key_edit_base'
+
+class Chef
+  class Knife
+    # Implements knife client key edit using Chef::Knife::KeyEdit
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the client that this key is for
+    class ClientKeyEdit < Knife
+      include Chef::Knife::KeyEditBase
+
+      banner 'knife client key edit CLIENT KEYNAME (options)'
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def actor_field_name
+        'client'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyEdit.new(@name, @actor, actor_field_name, ui, config)
+      end
+
+      def actor_missing_error
+        'You must specify a client name'
+      end
+
+      def keyname_missing_error
+        'You must specify a key name'
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+        @name = params[1]
+        if @name.nil?
+          show_usage
+          ui.fatal(keyname_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/client_key_list.rb b/lib/chef/knife/client_key_list.rb
new file mode 100644
index 0000000..f6f29ae
--- /dev/null
+++ b/lib/chef/knife/client_key_list.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+require 'chef/knife/key_list_base'
+
+class Chef
+  class Knife
+    # Implements knife user key list using Chef::Knife::KeyList
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the client that this key is for
+    class ClientKeyList < Knife
+      include Chef::Knife::KeyListBase
+
+      banner "knife client key list CLIENT (options)"
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def list_method
+        :list_by_client
+      end
+
+      def actor_missing_error
+        'You must specify a client name'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config)
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/client_key_show.rb b/lib/chef/knife/client_key_show.rb
new file mode 100644
index 0000000..c39a279
--- /dev/null
+++ b/lib/chef/knife/client_key_show.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+
+class Chef
+  class Knife
+    # Implements knife client key show using Chef::Knife::KeyShow
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the client that this key is for
+    class ClientKeyShow < Knife
+      banner "knife client key show CLIENT KEYNAME (options)"
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def load_method
+        :load_by_client
+      end
+
+      def actor_missing_error
+        'You must specify a client name'
+      end
+
+      def keyname_missing_error
+        'You must specify a key name'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyShow.new(@name, @actor, load_method, ui)
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+        @name = params[1]
+        if @name.nil?
+          show_usage
+          ui.fatal(keyname_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/client_list.rb b/lib/chef/knife/client_list.rb
index da0bf12..d8a3698 100644
--- a/lib/chef/knife/client_list.rb
+++ b/lib/chef/knife/client_list.rb
@@ -23,7 +23,7 @@ class Chef
     class ClientList < Knife
 
       deps do
-        require 'chef/api_client'
+        require 'chef/api_client_v1'
         require 'chef/json_compat'
       end
 
@@ -35,7 +35,7 @@ class Chef
         :description => "Show corresponding URIs"
 
       def run
-        output(format_list_for_display(Chef::ApiClient.list))
+        output(format_list_for_display(Chef::ApiClientV1.list))
       end
     end
   end
diff --git a/lib/chef/knife/client_reregister.rb b/lib/chef/knife/client_reregister.rb
index 666fd09..b94761e 100644
--- a/lib/chef/knife/client_reregister.rb
+++ b/lib/chef/knife/client_reregister.rb
@@ -23,7 +23,7 @@ class Chef
     class ClientReregister < Knife
 
       deps do
-        require 'chef/api_client'
+        require 'chef/api_client_v1'
         require 'chef/json_compat'
       end
 
@@ -43,7 +43,7 @@ class Chef
           exit 1
         end
 
-        client = Chef::ApiClient.reregister(@client_name)
+        client = Chef::ApiClientV1.reregister(@client_name)
         Chef::Log.debug("Updated client data: #{client.inspect}")
         key = client.private_key
         if config[:file]
diff --git a/lib/chef/knife/client_show.rb b/lib/chef/knife/client_show.rb
index 822848f..bdac3f9 100644
--- a/lib/chef/knife/client_show.rb
+++ b/lib/chef/knife/client_show.rb
@@ -25,7 +25,7 @@ class Chef
       include Knife::Core::MultiAttributeReturnOption
 
       deps do
-        require 'chef/api_client'
+        require 'chef/api_client_v1'
         require 'chef/json_compat'
       end
 
@@ -40,7 +40,7 @@ class Chef
           exit 1
         end
 
-        client = Chef::ApiClient.load(@client_name)
+        client = Chef::ApiClientV1.load(@client_name)
         output(format_for_display(client))
       end
 
diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb
index e17a540..97f6e65 100644
--- a/lib/chef/knife/cookbook_create.rb
+++ b/lib/chef/knife/cookbook_create.rb
@@ -48,7 +48,7 @@ class Chef
       option :cookbook_copyright,
         :short => "-C COPYRIGHT",
         :long => "--copyright COPYRIGHT",
-        :description => "Name of Copyright holder"
+        :description => "Name of copyright holder"
 
       option :cookbook_email,
         :short => "-m EMAIL",
diff --git a/lib/chef/knife/cookbook_site_download.rb b/lib/chef/knife/cookbook_site_download.rb
index c2d72ef..3e586e6 100644
--- a/lib/chef/knife/cookbook_site_download.rb
+++ b/lib/chef/knife/cookbook_site_download.rb
@@ -84,7 +84,7 @@ class Chef
       end
 
       def download_cookbook
-        ui.info "Downloading #{@name_args[0]} from the cookbooks site at version #{version} to #{download_location}"
+        ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}"
         noauth_rest.sign_on_redirect = false
         tf = noauth_rest.get_rest desired_cookbook_data["file"], true
 
diff --git a/lib/chef/knife/cookbook_site_install.rb b/lib/chef/knife/cookbook_site_install.rb
index d0ab6da..cc68fe7 100644
--- a/lib/chef/knife/cookbook_site_install.rb
+++ b/lib/chef/knife/cookbook_site_install.rb
@@ -93,7 +93,7 @@ class Chef
 
         # TODO: it'd be better to store these outside the cookbook repo and
         # keep them around, e.g., in ~/Library/Caches on OS X.
-        ui.info("removing downloaded tarball")
+        ui.info("Removing downloaded tarball")
         File.unlink(upstream_file)
 
         if @repo.finalize_updates_to(@cookbook_name, downloader.version)
@@ -142,7 +142,11 @@ class Chef
       def extract_cookbook(upstream_file, version)
         ui.info("Uncompressing #{@cookbook_name} version #{version}.")
         # FIXME: Detect if we have the bad tar from git on Windows: https://github.com/opscode/chef/issues/1753
-        shell_out!("tar zxvf #{convert_path upstream_file}", :cwd => @install_path)
+        extract_command="tar zxvf \"#{convert_path upstream_file}\"" 
+        if Chef::Platform.windows?
+          extract_command << " --force-local"
+        end
+        shell_out!(extract_command, :cwd => @install_path)
       end
 
       def clear_existing_files(cookbook_path)
diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb
index efd2e7f..beb98b7 100644
--- a/lib/chef/knife/cookbook_site_share.rb
+++ b/lib/chef/knife/cookbook_site_share.rb
@@ -48,7 +48,7 @@ class Chef
         :short => '-n',
         :boolean => true,
         :default => false,
-        :description => "Don't take action, only print what files will be upload to SuperMarket."
+        :description => "Don't take action, only print what files will be uploaded to Supermarket."
 
       def run
         config[:cookbook_path] ||= Chef::Config[:cookbook_path]
@@ -94,7 +94,7 @@ class Chef
             Chef::Log.debug("Removing local staging directory at #{tmp_cookbook_dir}")
             FileUtils.rm_rf tmp_cookbook_dir
           rescue => e
-            ui.error("Error uploading cookbook #{cookbook_name} to the Opscode Cookbook Site: #{e.message}. Increase log verbosity (-VV) for more information.")
+            ui.error("Error uploading cookbook #{cookbook_name} to Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
             Chef::Log.debug("\n#{e.backtrace.join("\n")}")
             exit(1)
           end
@@ -108,15 +108,15 @@ class Chef
 
       def get_category(cookbook_name)
         begin
-          data = noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}")
+          data = noauth_rest.get_rest("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}")
           if !data["category"] && data["error_code"]
-            ui.fatal("Received an error from the Opscode Cookbook site: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.")
+            ui.fatal("Received an error from Supermarket: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.")
             exit(1)
           else
             data['category']
           end
         rescue => e
-          ui.fatal("Unable to reach Opscode Cookbook Site: #{e.message}. Increase log verbosity (-VV) for more information.")
+          ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
           Chef::Log.debug("\n#{e.backtrace.join("\n")}")
           exit(1)
         end
@@ -136,7 +136,7 @@ class Chef
         if http_resp.code.to_i != 201
           if res['error_messages']
             if res['error_messages'][0] =~ /Version already exists/
-              ui.error "The same version of this cookbook already exists on the Opscode Cookbook Site."
+              ui.error "The same version of this cookbook already exists on Supermarket."
               exit(1)
             else
               ui.error "#{res['error_messages'][0]}"
diff --git a/lib/chef/knife/cookbook_site_unshare.rb b/lib/chef/knife/cookbook_site_unshare.rb
index b34282e..77bb183 100644
--- a/lib/chef/knife/cookbook_site_unshare.rb
+++ b/lib/chef/knife/cookbook_site_unshare.rb
@@ -38,7 +38,7 @@ class Chef
           exit 1
         end
 
-        confirm "Do you really want to unshare the cookbook #{@cookbook_name}"
+        confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}"
 
         begin
           rest.delete_rest "https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}"
@@ -48,7 +48,7 @@ class Chef
           exit 1
         end
 
-        ui.info "Unshared cookbook #{@cookbook_name}"
+        ui.info "Unshared all versions of the cookbook #{@cookbook_name}"
       end
 
     end
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index 7197653..fca3b0a 100644
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ b/lib/chef/knife/core/bootstrap_context.rb
@@ -40,7 +40,7 @@ class Chef
         end
 
         def bootstrap_environment
-          @chef_config[:environment] || '_default'
+          @config[:environment]
         end
 
         def validation_key
@@ -128,7 +128,8 @@ CONFIG
           client_path = @chef_config[:chef_client_path] || 'chef-client'
           s = "#{client_path} -j /etc/chef/first-boot.json"
           s << ' -l debug' if @config[:verbosity] and @config[:verbosity] >= 2
-          s << " -E #{bootstrap_environment}"
+          s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil?
+          s << " --no-color" unless @config[:color]
           s
         end
 
@@ -163,11 +164,19 @@ CONFIG
         end
 
         def first_boot
-          (@config[:first_boot_attributes] || {}).merge(:run_list => @run_list)
+          (@config[:first_boot_attributes] || {}).tap do |attributes|
+            if @config[:policy_name] && @config[:policy_group]
+              attributes.merge!(:policy_name => @config[:policy_name], :policy_group => @config[:policy_group])
+            else
+              attributes.merge!(:run_list => @run_list)
+            end
+
+            attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty?
+          end
         end
 
         private
-       
+
         # Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped
         # This string should contain both the commands necessary to both create the files, as well as their content
         def trusted_certs_content
diff --git a/lib/chef/knife/core/custom_manifest_loader.rb b/lib/chef/knife/core/custom_manifest_loader.rb
new file mode 100644
index 0000000..c19e749
--- /dev/null
+++ b/lib/chef/knife/core/custom_manifest_loader.rb
@@ -0,0 +1,69 @@
+# Copyright:: Copyright (c) 2015 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/version'
+class Chef
+  class Knife
+    class SubcommandLoader
+
+      #
+      # Load a subcommand from a user-supplied
+      # manifest file
+      #
+      class CustomManifestLoader < Chef::Knife::SubcommandLoader
+        attr_accessor :manifest
+        def initialize(chef_config_dir, plugin_manifest)
+          super(chef_config_dir)
+          @manifest = plugin_manifest
+        end
+
+        # If the user has created a ~/.chef/plugin_manifest.json file, we'll use
+        # that instead of inspecting the on-system gems to find the plugins. The
+        # file format is expected to look like:
+        #
+        #   { "plugins": {
+        #       "knife-ec2": {
+        #         "paths": [
+        #           "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb",
+        #           "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb"
+        #         ]
+        #       }
+        #     }
+        #   }
+        #
+        # Extraneous content in this file is ignored. This is intentional so that we
+        # can adapt the file format for potential behavior changes to knife in
+        # the future.
+        def find_subcommands_via_manifest
+          # Format of subcommand_files is "relative_path" (something you can
+          # Kernel.require()) => full_path. The relative path isn't used
+          # currently, so we just map full_path => full_path.
+          subcommand_files = {}
+          manifest["plugins"].each do |plugin_name, plugin_manifest|
+            plugin_manifest["paths"].each do |cmd_path|
+              subcommand_files[cmd_path] = cmd_path
+            end
+          end
+          subcommand_files.merge(find_subcommands_via_dirglob)
+        end
+
+        def subcommand_files
+          subcommand_files ||= (find_subcommands_via_manifest.values + site_subcommands).flatten.uniq
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/core/gem_glob_loader.rb b/lib/chef/knife/core/gem_glob_loader.rb
new file mode 100644
index 0000000..d09131a
--- /dev/null
+++ b/lib/chef/knife/core/gem_glob_loader.rb
@@ -0,0 +1,138 @@
+# Author:: Christopher Brown (<cb at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2009-2015 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/version'
+require 'chef/util/path_helper'
+class Chef
+  class Knife
+    class SubcommandLoader
+      class GemGlobLoader < Chef::Knife::SubcommandLoader
+        MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
+        MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
+
+        def subcommand_files
+          @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
+        end
+
+        # Returns a Hash of paths to knife commands built-in to chef, or installed via gem.
+        # If rubygems is not installed, falls back to globbing the knife directory.
+        # The Hash is of the form {"relative/path" => "/absolute/path"}
+        #--
+        # Note: the "right" way to load the plugins is to require the relative path, i.e.,
+        #   require 'chef/knife/command'
+        # but we're getting frustrated by bugs at every turn, and it's slow besides. So
+        # subcommand loader has been modified to load the plugins by using Kernel.load
+        # with the absolute path.
+        def gem_and_builtin_subcommands
+          require 'rubygems'
+          find_subcommands_via_rubygems
+        rescue LoadError
+          find_subcommands_via_dirglob
+        end
+
+        def find_subcommands_via_dirglob
+          # The "require paths" of the core knife subcommands bundled with chef
+          files = Dir[File.join(Chef::Util::PathHelper.escape_glob(File.expand_path('../../../knife', __FILE__)), '*.rb')]
+          subcommand_files = {}
+          files.each do |knife_file|
+            rel_path = knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/,1]
+            subcommand_files[rel_path] = knife_file
+          end
+          subcommand_files
+        end
+
+        def find_subcommands_via_rubygems
+          files = find_files_latest_gems 'chef/knife/*.rb'
+          subcommand_files = {}
+          files.each do |file|
+            rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
+
+            # When not installed as a gem (ChefDK/appbundler in particular), AND
+            # a different version of Chef is installed via gems, `files` will
+            # include some files from the 'other' Chef install. If this contains
+            # a knife command that doesn't exist in this version of Chef, we will
+            # get a LoadError later when we try to require it.
+            next if from_different_chef_version?(file)
+
+            subcommand_files[rel_path] = file
+          end
+
+          subcommand_files.merge(find_subcommands_via_dirglob)
+        end
+
+        private
+
+        def find_files_latest_gems(glob, check_load_path=true)
+          files = []
+
+          if check_load_path
+            files = $LOAD_PATH.map { |load_path|
+              Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob(load_path)}#{Gem.suffix_pattern}"]
+            }.flatten.select { |file| File.file? file.untaint }
+          end
+
+          gem_files = latest_gem_specs.map do |spec|
+            # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
+            if spec.respond_to? :matches_for_glob
+              spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
+            else
+              check_spec_for_glob(spec, glob)
+            end
+          end.flatten
+
+          files.concat gem_files
+          files.uniq! if check_load_path
+
+          return files
+        end
+
+        def latest_gem_specs
+          @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
+                                  Gem::Specification.latest_specs(true)  # find prerelease gems
+                                else
+                                  Gem.source_index.latest_specs(true)
+                                end
+        end
+
+        def check_spec_for_glob(spec, glob)
+          dirs = if spec.require_paths.size > 1 then
+                   "{#{spec.require_paths.join(',')}}"
+                 else
+                   spec.require_paths.first
+                 end
+
+          glob = File.join(Chef::Util::PathHelper.escape_glob(spec.full_gem_path, dirs), glob)
+
+          Dir[glob].map { |f| f.untaint }
+        end
+
+        def from_different_chef_version?(path)
+          matches_any_chef_gem?(path) && !matches_this_chef_gem?(path)
+        end
+
+        def matches_any_chef_gem?(path)
+          path =~ MATCHES_CHEF_GEM
+        end
+
+        def matches_this_chef_gem?(path)
+          path =~ MATCHES_THIS_CHEF_GEM
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/core/generic_presenter.rb b/lib/chef/knife/core/generic_presenter.rb
index f3ea0f0..2df9603 100644
--- a/lib/chef/knife/core/generic_presenter.rb
+++ b/lib/chef/knife/core/generic_presenter.rb
@@ -181,7 +181,7 @@ class Chef
             # Must check :[] before attr because spec can include
             #   `keys` - want the key named `keys`, not a list of
             #   available keys.
-            elsif data.respond_to?(:[])
+            elsif data.respond_to?(:[])  && data.has_key?(attr)
               data = data[attr]
             elsif data.respond_to?(attr.to_sym)
               data = data.send(attr.to_sym)
diff --git a/lib/chef/knife/core/hashed_command_loader.rb b/lib/chef/knife/core/hashed_command_loader.rb
new file mode 100644
index 0000000..6eb3635
--- /dev/null
+++ b/lib/chef/knife/core/hashed_command_loader.rb
@@ -0,0 +1,80 @@
+# Author:: Steven Danna (<steve at chef.io>)
+# Copyright:: Copyright (c) 2015 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/version'
+class Chef
+  class Knife
+    class SubcommandLoader
+      #
+      # Load a subcommand from a pre-computed path
+      # for the given command.
+      #
+      class HashedCommandLoader < Chef::Knife::SubcommandLoader
+        KEY = '_autogenerated_command_paths'
+
+        attr_accessor :manifest
+        def initialize(chef_config_dir, plugin_manifest)
+          super(chef_config_dir)
+          @manifest = plugin_manifest
+        end
+
+        def guess_category(args)
+          category_words = positional_arguments(args)
+          category_words.map! { |w| w.split('-') }.flatten!
+          find_longest_key(manifest[KEY]["plugins_by_category"], category_words, ' ')
+        end
+
+        def list_commands(pref_category=nil)
+          if pref_category || manifest[KEY]["plugins_by_category"].key?(pref_category)
+            { pref_category => manifest[KEY]["plugins_by_category"][pref_category] }
+          else
+            manifest[KEY]["plugins_by_category"]
+          end
+        end
+
+        def subcommand_files
+          manifest[KEY]["plugins_paths"].values.flatten
+        end
+
+        def load_command(args)
+          paths = manifest[KEY]["plugins_paths"][subcommand_for_args(args)]
+          if paths.nil? || paths.empty? || (! paths.is_a? Array)
+            false
+          else
+            paths.each do |sc|
+              if File.exists?(sc)
+                Kernel.load sc
+              else
+                Chef::Log.error "The file #{sc} is missing for subcommand '#{subcommand_for_args(args)}'. Please rehash to update the subcommands cache."
+                return false
+              end
+            end
+            true
+          end
+        end
+
+        def subcommand_for_args(args)
+          if manifest[KEY]["plugins_paths"].key?(args)
+            args
+          else
+            find_longest_key(manifest[KEY]["plugins_paths"], args, "_")
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/core/node_presenter.rb b/lib/chef/knife/core/node_presenter.rb
index d1aab59..5192c53 100644
--- a/lib/chef/knife/core/node_presenter.rb
+++ b/lib/chef/knife/core/node_presenter.rb
@@ -67,7 +67,12 @@ class Chef
             result = {}
 
             result["name"] = node.name
-            result["chef_environment"] = node.chef_environment
+            if node.policy_name.nil? && node.policy_group.nil?
+              result["chef_environment"] = node.chef_environment
+            else
+              result["policy_name"] = node.policy_name
+              result["policy_group"] = node.policy_group
+            end
             result["run_list"] = node.run_list
             result["normal"] = node.normal_attrs
 
@@ -95,14 +100,32 @@ class Chef
 
             summarized=<<-SUMMARY
 #{ui.color('Node Name:', :bold)}   #{ui.color(node.name, :bold)}
+SUMMARY
+            show_policy = !(node.policy_name.nil? && node.policy_group.nil?)
+            if show_policy
+              summarized << <<-POLICY
+#{key('Policy Name:')}  #{node.policy_name}
+#{key('Policy Group:')} #{node.policy_group}
+POLICY
+            else
+              summarized << <<-ENV
 #{key('Environment:')} #{node.chef_environment}
+ENV
+            end
+            summarized << <<-SUMMARY
 #{key('FQDN:')}        #{node[:fqdn]}
 #{key('IP:')}          #{ip}
 #{key('Run List:')}    #{node.run_list}
+SUMMARY
+            unless show_policy
+              summarized << <<-ROLES
 #{key('Roles:')}       #{Array(node[:roles]).join(', ')}
+ROLES
+            end
+            summarized << <<-SUMMARY
 #{key('Recipes:')}     #{Array(node[:recipes]).join(', ')}
 #{key('Platform:')}    #{node[:platform]} #{node[:platform_version]}
-#{key('Tags:')}        #{Array(node[:tags]).join(', ')}
+#{key('Tags:')}        #{node.tags.join(', ')}
 SUMMARY
             if config[:medium_output] || config[:long_output]
               summarized +=<<-MORE
diff --git a/lib/chef/knife/core/object_loader.rb b/lib/chef/knife/core/object_loader.rb
index 698b09a..97ca381 100644
--- a/lib/chef/knife/core/object_loader.rb
+++ b/lib/chef/knife/core/object_loader.rb
@@ -18,6 +18,7 @@
 
 require 'ffi_yajl'
 require 'chef/util/path_helper'
+require 'chef/data_bag_item'
 
 class Chef
   class Knife
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index 1f59515..808e053 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -18,105 +18,120 @@
 
 require 'chef/version'
 require 'chef/util/path_helper'
+require 'chef/knife/core/gem_glob_loader'
+require 'chef/knife/core/hashed_command_loader'
+require 'chef/knife/core/custom_manifest_loader'
+
 class Chef
   class Knife
+    #
+    # Public Methods of a Subcommand Loader
+    #
+    # load_commands            - loads all available subcommands
+    # load_command(args)       - loads subcommands for the given args
+    # list_commands(args)      - lists all available subcommands,
+    #                            optionally filtering by category
+    # subcommand_files         - returns an array of all subcommand files
+    #                            that could be loaded
+    # commnad_class_from(args) - returns the subcommand class for the
+    #                            user-requested command
+    #
     class SubcommandLoader
-
-      MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
-      MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}/}.freeze
-
       attr_reader :chef_config_dir
       attr_reader :env
 
-      def initialize(chef_config_dir, env=nil)
+      # A small factory method.  Eventually, this is the only place
+      # where SubcommandLoader should know about its subclasses, but
+      # to maintain backwards compatibility many of the instance
+      # methods in this base class contain default implementations
+      # of the functions sub classes should otherwise provide
+      # or directly instantiate the appropriate subclass
+      def self.for_config(chef_config_dir)
+        if autogenerated_manifest?
+          Chef::Log.debug("Using autogenerated hashed command manifest #{plugin_manifest_path}")
+          Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest)
+        elsif custom_manifest?
+          Chef.log_deprecation("Using custom manifest #{plugin_manifest_path} is deprecated.  Please use a `knife rehash` autogenerated manifest instead.")
+          Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, plugin_manifest)
+        else
+          Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir)
+        end
+      end
+
+      def self.plugin_manifest?
+        plugin_manifest_path && File.exist?(plugin_manifest_path)
+      end
+
+      def self.autogenerated_manifest?
+        plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY)
+      end
+
+      def self.custom_manifest?
+        plugin_manifest? && plugin_manifest.key?("plugins")
+      end
+
+      def self.plugin_manifest
+        Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
+      end
+
+      def self.plugin_manifest_path
+        Chef::Util::PathHelper.home('.chef', 'plugin_manifest.json')
+      end
+
+      def initialize(chef_config_dir, env = nil)
         @chef_config_dir = chef_config_dir
-        @forced_activate = {}
 
         # Deprecated and un-used instance variable.
         @env = env
         unless env.nil?
-          Chef::Log.deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.")
+          Chef.log_deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.")
         end
       end
 
       # Load all the sub-commands
       def load_commands
+        return true if @loaded
         subcommand_files.each { |subcommand| Kernel.load subcommand }
-        true
+        @loaded = true
       end
 
-      # Returns an Array of paths to knife commands located in chef_config_dir/plugins/knife/
-      # and ~/.chef/plugins/knife/
-      def site_subcommands
-        user_specific_files = []
-
-        if chef_config_dir
-          user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob(chef_config_dir)))
-        end
-
-        # finally search ~/.chef/plugins/knife/*.rb
-        Chef::Util::PathHelper.home('.chef', 'plugins', 'knife') do |p|
-          user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(p), '*.rb'))
-        end
+      def force_load
+        @loaded=false
+        load_commands
+      end
 
-        user_specific_files
+      def load_command(_command_args)
+        load_commands
       end
 
-      # Returns a Hash of paths to knife commands built-in to chef, or installed via gem.
-      # If rubygems is not installed, falls back to globbing the knife directory.
-      # The Hash is of the form {"relative/path" => "/absolute/path"}
-      #--
-      # Note: the "right" way to load the plugins is to require the relative path, i.e.,
-      #   require 'chef/knife/command'
-      # but we're getting frustrated by bugs at every turn, and it's slow besides. So
-      # subcommand loader has been modified to load the plugins by using Kernel.load
-      # with the absolute path.
-      def gem_and_builtin_subcommands
-        if have_plugin_manifest?
-          find_subcommands_via_manifest
+      def list_commands(pref_cat = nil)
+        load_commands
+        if pref_cat && Chef::Knife.subcommands_by_category.key?(pref_cat)
+          { pref_cat => Chef::Knife.subcommands_by_category[pref_cat] }
         else
-          # search all gems for chef/knife/*.rb
-          require 'rubygems'
-          find_subcommands_via_rubygems
+          Chef::Knife.subcommands_by_category
         end
-      rescue LoadError
-        find_subcommands_via_dirglob
       end
 
-      def subcommand_files
-        @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
+      def command_class_from(args)
+        cmd_words = positional_arguments(args)
+        load_command(cmd_words)
+        result = Chef::Knife.subcommands[find_longest_key(Chef::Knife.subcommands,
+                                                          cmd_words, '_')]
+        result || Chef::Knife.subcommands[args.first.gsub('-', '_')]
       end
 
-      # If the user has created a ~/.chef/plugin_manifest.json file, we'll use
-      # that instead of inspecting the on-system gems to find the plugins. The
-      # file format is expected to look like:
-      #
-      #   { "plugins": {
-      #       "knife-ec2": {
-      #         "paths": [
-      #           "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb",
-      #           "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb"
-      #         ]
-      #       }
-      #     }
-      #   }
-      #
-      # Extraneous content in this file is ignored. This intentional so that we
-      # can adapt the file format for potential behavior changes to knife in
-      # the future.
-      def find_subcommands_via_manifest
-        # Format of subcommand_files is "relative_path" (something you can
-        # Kernel.require()) => full_path. The relative path isn't used
-        # currently, so we just map full_path => full_path.
-        subcommand_files = {}
-        plugin_manifest["plugins"].each do |plugin_name, plugin_manifest|
-          plugin_manifest["paths"].each do |cmd_path|
-            subcommand_files[cmd_path] = cmd_path
-          end
-        end
-        subcommand_files.merge(find_subcommands_via_dirglob)
+      def guess_category(args)
+        category_words = positional_arguments(args)
+        category_words.map! { |w| w.split('-') }.flatten!
+        find_longest_key(Chef::Knife.subcommands_by_category,
+                         category_words, ' ')
       end
 
+
+      #
+      # This is shared between the custom_manifest_loader and the gem_glob_loader
+      #
       def find_subcommands_via_dirglob
         # The "require paths" of the core knife subcommands bundled with chef
         files = Dir[File.join(Chef::Util::PathHelper.escape_glob(File.expand_path('../../../knife', __FILE__)), '*.rb')]
@@ -128,95 +143,65 @@ class Chef
         subcommand_files
       end
 
-      def find_subcommands_via_rubygems
-        files = find_files_latest_gems 'chef/knife/*.rb'
-        subcommand_files = {}
-        files.each do |file|
-          rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
-
-          # When not installed as a gem (ChefDK/appbundler in particular), AND
-          # a different version of Chef is installed via gems, `files` will
-          # include some files from the 'other' Chef install. If this contains
-          # a knife command that doesn't exist in this version of Chef, we will
-          # get a LoadError later when we try to require it.
-          next if from_different_chef_version?(file)
-
-          subcommand_files[rel_path] = file
-        end
-
-        subcommand_files.merge(find_subcommands_via_dirglob)
-      end
-
-      def have_plugin_manifest?
-        plugin_manifest_path && File.exist?(plugin_manifest_path)
-      end
-
-      def plugin_manifest
-        Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
-      end
-
-      def plugin_manifest_path
-        Chef::Util::PathHelper.home('.chef', 'plugin_manifest.json')
+      #
+      # Subclassses should define this themselves.  Eventually, this will raise a
+      # NotImplemented error, but for now, we mimic the behavior the user was likely
+      # to get in the past.
+      #
+      def subcommand_files
+        Chef.log_deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated.
+Please use Chef::Knife::SubcommandLoader.for_config(chef_config_dir, env)"
+        @subcommand_files ||= if Chef::Knife::SubcommandLoader.plugin_manifest?
+                                Chef::Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, env).subcommand_files
+                              else
+                                Chef::Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir, env).subcommand_files
+                              end
       end
 
-      private
-
-      def find_files_latest_gems(glob, check_load_path=true)
-        files = []
-
-        if check_load_path
-          files = $LOAD_PATH.map { |load_path|
-            Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob(load_path)}#{Gem.suffix_pattern}"]
-          }.flatten.select { |file| File.file? file.untaint }
-        end
-
-        gem_files = latest_gem_specs.map do |spec|
-          # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
-          if spec.respond_to? :matches_for_glob
-            spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
+      #
+      # Utility function for finding an element in a hash given an array
+      # of words and a separator.  We find the the longest key in the
+      # hash composed of the given words joined by the separator.
+      #
+      def find_longest_key(hash, words, sep = '_')
+        match = nil
+        until match || words.empty?
+          candidate = words.join(sep)
+          if hash.key?(candidate)
+            match = candidate
           else
-            check_spec_for_glob(spec, glob)
+            words.pop
           end
-        end.flatten
-
-        files.concat gem_files
-        files.uniq! if check_load_path
-
-        return files
-      end
-
-      def latest_gem_specs
-        @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
-          Gem::Specification.latest_specs(true)  # find prerelease gems
-        else
-          Gem.source_index.latest_specs(true)
         end
+        match
       end
 
-      def check_spec_for_glob(spec, glob)
-        dirs = if spec.require_paths.size > 1 then
-          "{#{spec.require_paths.join(',')}}"
-        else
-          spec.require_paths.first
-        end
-
-        glob = File.join(Chef::Util::PathHelper.escape_glob(spec.full_gem_path, dirs), glob)
-
-        Dir[glob].map { |f| f.untaint }
+      #
+      # The positional arguments from the argument list provided by the
+      # users. Used to search for subcommands and categories.
+      #
+      # @return [Array<String>]
+      #
+      def positional_arguments(args)
+        args.select { |arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
       end
 
-      def from_different_chef_version?(path)
-        matches_any_chef_gem?(path) && !matches_this_chef_gem?(path)
-      end
+      # Returns an Array of paths to knife commands located in
+      # chef_config_dir/plugins/knife/ and ~/.chef/plugins/knife/
+      def site_subcommands
+        user_specific_files = []
 
-      def matches_any_chef_gem?(path)
-        path =~ MATCHES_CHEF_GEM
-      end
+        if chef_config_dir
+          user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob(chef_config_dir)))
+        end
 
-      def matches_this_chef_gem?(path)
-        path =~ MATCHES_THIS_CHEF_GEM
-      end
+        # finally search ~/.chef/plugins/knife/*.rb
+        Chef::Util::PathHelper.home('.chef', 'plugins', 'knife') do |p|
+          user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(p), '*.rb'))
+        end
 
+        user_specific_files
+      end
     end
   end
 end
diff --git a/lib/chef/knife/key_create.rb b/lib/chef/knife/key_create.rb
new file mode 100644
index 0000000..5ee36e9
--- /dev/null
+++ b/lib/chef/knife/key_create.rb
@@ -0,0 +1,108 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+  class Knife
+    # Service class for UserKeyCreate and ClientKeyCreate,
+    # Implements common functionality of knife [user | org client] key create.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_accessor [Hash] cli input, see UserKeyCreate and ClientKeyCreate for what could populate it
+    class KeyCreate
+
+      attr_accessor :config
+
+      def initialize(actor, actor_field_name, ui, config)
+        @actor = actor
+        @actor_field_name = actor_field_name
+        @ui = ui
+        @config = config
+      end
+
+      def public_key_or_key_name_error_msg
+<<EOS
+You must pass either --public-key or --key-name, or both.
+If you only pass --public-key, a key name will be generated from the fingerprint of your key.
+If you only pass --key-name, a key pair will be generated by the server.
+EOS
+      end
+
+      def edit_data(key)
+        @ui.edit_data(key)
+      end
+
+      def display_info(input)
+        @ui.info(input)
+      end
+
+      def display_private_key(private_key)
+        @ui.msg(private_key)
+      end
+
+      def output_private_key_to_file(private_key)
+        File.open(@config[:file], "w") do |f|
+          f.print(private_key)
+        end
+      end
+
+      def create_key_from_hash(output)
+        Chef::Key.from_hash(output).create
+      end
+
+      def run
+        key = Chef::Key.new(@actor, @actor_field_name)
+        if !@config[:public_key] && !@config[:key_name]
+          raise Chef::Exceptions::KeyCommandInputError, public_key_or_key_name_error_msg
+        elsif !@config[:public_key]
+          key.create_key(true)
+        end
+
+        if @config[:public_key]
+          key.public_key(File.read(File.expand_path(@config[:public_key])))
+        end
+
+        if @config[:key_name]
+          key.name(@config[:key_name])
+        end
+
+        if @config[:expiration_date]
+          key.expiration_date(@config[:expiration_date])
+        else
+          key.expiration_date("infinity")
+        end
+
+        output = edit_data(key)
+        key = create_key_from_hash(output)
+
+        display_info("Created key: #{key.name}")
+        if key.private_key
+          if @config[:file]
+            output_private_key_to_file(key.private_key)
+          else
+            display_private_key(key.private_key)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/key_create_base.rb b/lib/chef/knife/key_create_base.rb
new file mode 100644
index 0000000..da31f70
--- /dev/null
+++ b/lib/chef/knife/key_create_base.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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.
+#
+
+class Chef
+  class Knife
+    # Extendable module that class_eval's common options into UserKeyCreate and ClientKeyCreate
+    #
+    # @author Tyler Cloke
+    module KeyCreateBase
+      def self.included(includer)
+        includer.class_eval do
+          option :public_key,
+                 :short => "-p FILENAME",
+                 :long => "--public-key FILENAME",
+                 :description => "Public key for newly created key. If not passed, the server will create a key pair for you, but you must pass --key-name NAME in that case."
+
+          option :file,
+                 :short => "-f FILE",
+                 :long  => "--file FILE",
+                 :description => "Write the private key to a file, if you requested the server to create one."
+
+          option :key_name,
+                 :short => "-k NAME",
+                 :long  => "--key-name NAME",
+                 :description => "The name for your key. If you do not pass a name, you must pass --public-key, and the name will default to the fingerprint of the public key passed."
+
+          option :expiration_date,
+                 :short => "-e DATE",
+                 :long  => "--expiration-date DATE",
+                 :description => "Optionally pass the expiration date for the key in ISO 8601 fomatted string: YYYY-MM-DDTHH:MM:SSZ e.g. 2013-12-24T21:00:00Z. Defaults to infinity if not passed. UTC timezone assumed."
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/key_delete.rb b/lib/chef/knife/key_delete.rb
new file mode 100644
index 0000000..fb996cf
--- /dev/null
+++ b/lib/chef/knife/key_delete.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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/key'
+
+class Chef
+  class Knife
+    # Service class for UserKeyDelete and ClientKeyDelete, used to delete keys.
+    # Implements common functionality of knife [user | org client] key delete.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_accessor [Hash] cli input, see UserKeyDelete and ClientKeyDelete for what could populate it
+    class KeyDelete
+      def initialize(name, actor, actor_field_name, ui)
+        @name = name
+        @actor = actor
+        @actor_field_name = actor_field_name
+        @ui = ui
+      end
+
+      def confirm!
+        @ui.confirm("Do you really want to delete the key named #{@name} for the #{@actor_field_name} named #{@actor}")
+      end
+
+      def print_destroyed
+        @ui.info("Destroyed key named #{@name} for the #{@actor_field_name} named #{@actor}")
+      end
+
+      def run
+        key = Chef::Key.new(@actor, @actor_field_name)
+        key.name(@name)
+        confirm!
+        key.destroy
+        print_destroyed
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/knife/key_edit.rb b/lib/chef/knife/key_edit.rb
new file mode 100644
index 0000000..48ae344
--- /dev/null
+++ b/lib/chef/knife/key_edit.rb
@@ -0,0 +1,114 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+  class Knife
+    # Service class for UserKeyEdit and ClientKeyEdit,
+    # Implements common functionality of knife [user | org client] key edit.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_accessor [Hash] cli input, see UserKeyEdit and ClientKeyEdit for what could populate it
+    class KeyEdit
+
+      attr_accessor :config
+
+      def initialize(original_name, actor, actor_field_name, ui, config)
+        @original_name = original_name
+        @actor = actor
+        @actor_field_name = actor_field_name
+        @ui = ui
+        @config = config
+      end
+
+      def public_key_and_create_key_error_msg
+<<EOS
+You passed both --public-key and --create-key. Only pass one, or the other, or neither.
+Do not pass either if you do not want to change the public_key field of your key.
+Pass --public-key if you want to update the public_key field of your key from a specific public key.
+Pass --create-key if you want the server to generate a new key and use that to update the public_key field of your key.
+EOS
+      end
+
+      def edit_data(key)
+        @ui.edit_data(key)
+      end
+
+      def display_info(input)
+        @ui.info(input)
+      end
+
+      def display_private_key(private_key)
+        @ui.msg(private_key)
+      end
+
+      def output_private_key_to_file(private_key)
+        File.open(@config[:file], "w") do |f|
+          f.print(private_key)
+        end
+      end
+
+      def update_key_from_hash(output)
+        Chef::Key.from_hash(output).update(@original_name)
+      end
+
+      def run
+        key = Chef::Key.new(@actor, @actor_field_name)
+        if @config[:public_key] && @config[:create_key]
+          raise Chef::Exceptions::KeyCommandInputError, public_key_and_create_key_error_msg
+        end
+
+        if @config[:create_key]
+          key.create_key(true)
+        end
+
+        if @config[:public_key]
+          key.public_key(File.read(File.expand_path(@config[:public_key])))
+        end
+
+        if @config[:key_name]
+          key.name(@config[:key_name])
+        else
+          key.name(@original_name)
+        end
+
+        if @config[:expiration_date]
+          key.expiration_date(@config[:expiration_date])
+        end
+
+        output = edit_data(key)
+        key = update_key_from_hash(output)
+
+        to_display = "Updated key: #{key.name}"
+        to_display << " (formally #{@original_name})" if key.name != @original_name
+        display_info(to_display)
+        if key.private_key
+          if @config[:file]
+            output_private_key_to_file(key.private_key)
+          else
+            display_private_key(key.private_key)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/key_edit_base.rb b/lib/chef/knife/key_edit_base.rb
new file mode 100644
index 0000000..bb5a951
--- /dev/null
+++ b/lib/chef/knife/key_edit_base.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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.
+#
+
+class Chef
+  class Knife
+    # Extendable module that class_eval's common options into UserKeyEdit and ClientKeyEdit
+    #
+    # @author Tyler Cloke
+    module KeyEditBase
+      def self.included(includer)
+        includer.class_eval do
+          option :public_key,
+                 :short => "-p FILENAME",
+                 :long => "--public-key FILENAME",
+                 :description => "Replace the public_key field from a file on disk. If not passed, the public_key field will not change."
+
+          option :create_key,
+                 :short => "-c",
+                 :long => "--create-key",
+                 :description => "Replace the public_key field with a key generated by the server. The private key will be returned."
+
+          option :file,
+                 :short => "-f FILE",
+                 :long  => "--file FILE",
+                 :description => "Write the private key to a file, if you requested the server to create one via --create-key."
+
+          option :key_name,
+                 :short => "-k NAME",
+                 :long  => "--key-name NAME",
+                 :description => "The new name for your key. Pass if you wish to update the name field of your key."
+
+          option :expiration_date,
+                 :short => "-e DATE",
+                 :long  => "--expiration-date DATE",
+                 :description => "Updates the expiration_date field of your key if passed. Pass in ISO 8601 fomatted string: YYYY-MM-DDTHH:MM:SSZ e.g. 2013-12-24T21:00:00Z or infinity. UTC timezone assumed."
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/key_list.rb b/lib/chef/knife/key_list.rb
new file mode 100644
index 0000000..e96a271
--- /dev/null
+++ b/lib/chef/knife/key_list.rb
@@ -0,0 +1,88 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+  class Knife
+    # Service class for UserKeyList and ClientKeyList, used to list keys.
+    # Implements common functionality of knife [user | org client] key list.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_accessor [Hash] cli input, see UserKeyList and ClientKeyList for what could populate it
+    class KeyList
+
+      attr_accessor :config
+
+      def initialize(actor, list_method, ui, config)
+        @actor = actor
+        @list_method = list_method
+        @ui = ui
+        @config = config
+      end
+
+      def expired_and_non_expired_msg
+<<EOS
+You cannot pass both --only-expired and --only-non-expired.
+Please pass one or none.
+EOS
+      end
+
+      def display_info(string)
+        @ui.output(string)
+      end
+
+      def colorize(string)
+        @ui.color(string, :cyan)
+      end
+
+      def run
+        if @config[:only_expired] && @config[:only_non_expired]
+          raise Chef::Exceptions::KeyCommandInputError, expired_and_non_expired_msg
+        end
+
+        # call proper list function
+        keys = Chef::Key.send(@list_method, @actor)
+        if @config[:with_details]
+          max_length = 0
+          keys.each do |key|
+            key['name'] = key['name'] + ":"
+            max_length = key['name'].length if key['name'].length > max_length
+          end
+          keys.each do |key|
+            next if !key['expired'] && @config[:only_expired]
+            next if key['expired'] && @config[:only_non_expired]
+            display = "#{colorize(key['name'].ljust(max_length))} #{key['uri']}"
+            display = "#{display} (expired)" if key["expired"]
+            display_info(display)
+          end
+        else
+          keys.each do |key|
+            next if !key['expired'] && @config[:only_expired]
+            next if key['expired'] && @config[:only_non_expired]
+            display_info(key['name'])
+          end
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/knife/key_list_base.rb b/lib/chef/knife/key_list_base.rb
new file mode 100644
index 0000000..861db0d
--- /dev/null
+++ b/lib/chef/knife/key_list_base.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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.
+#
+
+class Chef
+  class Knife
+    # Extendable module that class_eval's common options into UserKeyList and ClientKeyList
+    #
+    # @author Tyler Cloke
+    module KeyListBase
+      def self.included(includer)
+        includer.class_eval do
+          option :with_details,
+                 :short => "-w",
+                 :long => "--with-details",
+                 :description => "Show corresponding URIs and whether the key has expired or not."
+
+          option :only_expired,
+                 :short => "-e",
+                 :long => "--only-expired",
+                 :description => "Only show expired keys."
+
+          option :only_non_expired,
+                 :short => "-n",
+                 :long => "--only-non-expired",
+                 :description => "Only show non-expired keys."
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/key_show.rb b/lib/chef/knife/key_show.rb
new file mode 100644
index 0000000..522f7a1
--- /dev/null
+++ b/lib/chef/knife/key_show.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+  class Knife
+    # Service class for UserKeyShow and ClientKeyShow, used to show keys.
+    # Implements common functionality of knife [user | org client] key show.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_accessor [Hash] cli input, see UserKeyShow and ClientKeyShow for what could populate it
+    class KeyShow
+
+      attr_accessor :config
+
+      def initialize(name, actor, load_method, ui)
+        @name = name
+        @actor = actor
+        @load_method = load_method
+        @ui = ui
+      end
+
+      def display_output(key)
+        @ui.output(@ui.format_for_display(key))
+      end
+
+      def run
+        key = Chef::Key.send(@load_method, @actor, @name)
+        key.public_key(key.public_key.strip)
+        display_output(key)
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/node_run_list_remove.rb b/lib/chef/knife/node_run_list_remove.rb
index 4b8953a..ef03c17 100644
--- a/lib/chef/knife/node_run_list_remove.rb
+++ b/lib/chef/knife/node_run_list_remove.rb
@@ -42,7 +42,18 @@ class Chef
           entries = @name_args[1].split(',').map { |e| e.strip }
         end
 
-        entries.each { |e| node.run_list.remove(e) }
+        # iterate over the list of things to remove,
+        # warning if one of them was not found
+        entries.each do |e|
+          if node.run_list.find { |rli| e == rli.to_s }
+            node.run_list.remove(e)
+          else
+            ui.warn "#{e} is not in the run list"
+            unless e =~ /^(recipe|role)\[/
+              ui.warn '(did you forget recipe[] or role[] around it?)'
+            end
+          end
+        end
 
         node.save
 
diff --git a/lib/chef/knife/null.rb b/lib/chef/knife/null.rb
new file mode 100644
index 0000000..0b5058e
--- /dev/null
+++ b/lib/chef/knife/null.rb
@@ -0,0 +1,10 @@
+class Chef
+  class Knife
+    class Null < Chef::Knife
+      banner "knife null"
+
+      def run
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/osc_user_create.rb
similarity index 89%
copy from lib/chef/knife/user_create.rb
copy to lib/chef/knife/osc_user_create.rb
index 4130f06..6c34154 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/osc_user_create.rb
@@ -18,9 +18,13 @@
 
 require 'chef/knife'
 
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_create.rb.
 class Chef
   class Knife
-    class UserCreate < Knife
+    class OscUserCreate < Knife
 
       deps do
         require 'chef/user'
@@ -48,7 +52,7 @@ class Chef
         :long => "--user-key FILENAME",
         :description => "Public key for newly created user.  By default a key will be created for you."
 
-      banner "knife user create USER (options)"
+      banner "knife osc_user create USER (options)"
 
       def run
         @user_name = @name_args[0]
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/osc_user_delete.rb
similarity index 78%
copy from lib/chef/knife/user_delete.rb
copy to lib/chef/knife/osc_user_delete.rb
index b7af11b..5cd4f10 100644
--- a/lib/chef/knife/user_delete.rb
+++ b/lib/chef/knife/osc_user_delete.rb
@@ -18,16 +18,21 @@
 
 require 'chef/knife'
 
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in the user_delete.rb.
+
 class Chef
   class Knife
-    class UserDelete < Knife
+    class OscUserDelete < Knife
 
       deps do
         require 'chef/user'
         require 'chef/json_compat'
       end
 
-      banner "knife user delete USER (options)"
+      banner "knife osc_user delete USER (options)"
 
       def run
         @user_name = @name_args[0]
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/osc_user_edit.rb
similarity index 82%
copy from lib/chef/knife/user_edit.rb
copy to lib/chef/knife/osc_user_edit.rb
index ae319c8..526475d 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/osc_user_edit.rb
@@ -18,16 +18,21 @@
 
 require 'chef/knife'
 
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_edit.rb.
+
 class Chef
   class Knife
-    class UserEdit < Knife
+    class OscUserEdit < Knife
 
       deps do
         require 'chef/user'
         require 'chef/json_compat'
       end
 
-      banner "knife user edit USER (options)"
+      banner "knife osc_user edit USER (options)"
 
       def run
         @user_name = @name_args[0]
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/osc_user_list.rb
similarity index 78%
copy from lib/chef/knife/user_list.rb
copy to lib/chef/knife/osc_user_list.rb
index 5d2e735..84fca31 100644
--- a/lib/chef/knife/user_list.rb
+++ b/lib/chef/knife/osc_user_list.rb
@@ -18,16 +18,21 @@
 
 require 'chef/knife'
 
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_list.rb.
+
 class Chef
   class Knife
-    class UserList < Knife
+    class OscUserList < Knife
 
       deps do
         require 'chef/user'
         require 'chef/json_compat'
       end
 
-      banner "knife user list (options)"
+      banner "knife osc_user list (options)"
 
       option :with_uri,
         :short => "-w",
diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/osc_user_reregister.rb
similarity index 82%
copy from lib/chef/knife/user_reregister.rb
copy to lib/chef/knife/osc_user_reregister.rb
index 946150e..163b286 100644
--- a/lib/chef/knife/user_reregister.rb
+++ b/lib/chef/knife/osc_user_reregister.rb
@@ -18,16 +18,21 @@
 
 require 'chef/knife'
 
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_reregister.rb.
+
 class Chef
   class Knife
-    class UserReregister < Knife
+    class OscUserReregister < Knife
 
       deps do
         require 'chef/user'
         require 'chef/json_compat'
       end
 
-      banner "knife user reregister USER (options)"
+      banner "knife osc_user reregister USER (options)"
 
       option :file,
         :short => "-f FILE",
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/osc_user_show.rb
similarity index 80%
copy from lib/chef/knife/user_show.rb
copy to lib/chef/knife/osc_user_show.rb
index 61ea101..cb3a775 100644
--- a/lib/chef/knife/user_show.rb
+++ b/lib/chef/knife/osc_user_show.rb
@@ -18,9 +18,14 @@
 
 require 'chef/knife'
 
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_show.rb.
+
 class Chef
   class Knife
-    class UserShow < Knife
+    class OscUserShow < Knife
 
       include Knife::Core::MultiAttributeReturnOption
 
@@ -29,7 +34,7 @@ class Chef
         require 'chef/json_compat'
       end
 
-      banner "knife user show USER (options)"
+      banner "knife osc_user show USER (options)"
 
       def run
         @user_name = @name_args[0]
diff --git a/lib/chef/knife/rehash.rb b/lib/chef/knife/rehash.rb
new file mode 100644
index 0000000..6f1fd91
--- /dev/null
+++ b/lib/chef/knife/rehash.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Steven Danna <steve at chef.io>
+# Copyright:: Copyright (c) 2015 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/knife'
+require 'chef/knife/core/subcommand_loader'
+
+class Chef
+  class Knife
+    class Rehash <  Chef::Knife
+      banner "knife rehash"
+
+      def run
+        if ! Chef::Knife::SubcommandLoader.autogenerated_manifest?
+          ui.msg "Using knife-rehash will speed up knife's load time by caching the location of subcommands on disk."
+          ui.msg "However, you will need to update the cache by running `knife rehash` anytime you install a new knife plugin."
+        else
+          reload_plugins
+        end
+        write_hash(generate_hash)
+      end
+
+      def reload_plugins
+        Chef::Knife::SubcommandLoader::GemGlobLoader.new(@@chef_config_dir).load_commands
+      end
+
+      def generate_hash
+        output = if Chef::Knife::SubcommandLoader.plugin_manifest?
+                   Chef::Knife::SubcommandLoader.plugin_manifest
+                 else
+                   { Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY => {}}
+                 end
+        output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]['plugins_paths'] = Chef::Knife.subcommand_files
+        output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]['plugins_by_category'] = Chef::Knife.subcommands_by_category
+        output
+      end
+
+      def write_hash(data)
+        plugin_manifest_dir = File.expand_path('..', Chef::Knife::SubcommandLoader.plugin_manifest_path)
+        FileUtils.mkdir_p(plugin_manifest_dir) unless File.directory?(plugin_manifest_dir)
+        File.open(Chef::Knife::SubcommandLoader.plugin_manifest_path, 'w') do |f|
+          f.write(Chef::JSONCompat.to_json_pretty(data))
+          ui.msg "Knife subcommands are cached in #{Chef::Knife::SubcommandLoader.plugin_manifest_path}. Delete this file to disable the caching."
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb
index 9319d30..014fc8d 100644
--- a/lib/chef/knife/search.rb
+++ b/lib/chef/knife/search.rb
@@ -136,7 +136,7 @@ class Chef
       def read_cli_args
         if config[:query]
           if @name_args[1]
-            ui.error "please specify query as an argument or an option via -q, not both"
+            ui.error "Please specify query as an argument or an option via -q, not both"
             ui.msg opt_parser
             exit 1
           end
@@ -145,7 +145,7 @@ class Chef
         else
           case name_args.size
           when 0
-            ui.error "no query specified"
+            ui.error "No query specified"
             ui.msg opt_parser
             exit 1
           when 1
@@ -160,7 +160,7 @@ class Chef
 
       def fuzzify_query
         if @query !~ /:/
-          @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}*"
+          @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}* OR policy_name:*#{@query}* OR policy_group:*#{@query}*"
         end
       end
 
diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb
index 656baf5..99ae2f4 100644
--- a/lib/chef/knife/ssh.rb
+++ b/lib/chef/knife/ssh.rb
@@ -16,6 +16,7 @@
 # limitations under the License.
 #
 
+require 'chef/mixin/shell_out'
 require 'chef/knife'
 
 class Chef
@@ -29,7 +30,6 @@ class Chef
         require 'readline'
         require 'chef/exceptions'
         require 'chef/search/query'
-        require 'chef/mixin/shell_out'
         require 'chef/mixin/command'
         require 'chef/util/path_helper'
         require 'mixlib/shellout'
@@ -72,7 +72,7 @@ class Chef
         :description => "The ssh password - will prompt if flag is specified but no password is given",
         # default to a value that can not be a password (boolean)
         # so we can effectively test if this parameter was specified
-        # without a vlaue
+        # without a value
         :default => false
 
       option :ssh_port,
@@ -94,8 +94,12 @@ class Chef
         :boolean => true
 
       option :identity_file,
-        :short => "-i IDENTITY_FILE",
         :long => "--identity-file IDENTITY_FILE",
+        :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead."
+
+      option :ssh_identity_file,
+        :short => "-i IDENTITY_FILE",
+        :long => "--ssh-identity-file IDENTITY_FILE",
         :description => "The SSH identity file used for authentication"
 
       option :host_key_verify,
@@ -111,6 +115,12 @@ class Chef
         :boolean => true,
         :proc => Proc.new { :raise }
 
+      option :tmux_split,
+        :long => "--tmux-split",
+        :description => "Split tmux window.",
+        :boolean => true,
+        :default => false
+
       def session
         config[:on_error] ||= :skip
         ssh_error_handler = Proc.new do |server|
@@ -132,15 +142,19 @@ class Chef
         if config[:ssh_gateway]
           gw_host, gw_user = config[:ssh_gateway].split('@').reverse
           gw_host, gw_port = gw_host.split(':')
-          gw_opts = gw_port ? { :port => gw_port } : {}
+          gw_opts = session_options(gw_host, gw_port, gw_user)
+          user = gw_opts.delete(:user)
 
-          session.via(gw_host, gw_user || config[:ssh_user], gw_opts)
+          begin
+            # Try to connect with a key.
+            session.via(gw_host, user, gw_opts)
+          rescue Net::SSH::AuthenticationFailed
+            prompt = "Enter the password for #{user}@#{gw_host}: "
+            gw_opts[:password] = prompt_for_password(prompt)
+            # Try again with a password.
+            session.via(gw_host, user, gw_opts)
+          end
         end
-      rescue Net::SSH::AuthenticationFailed
-        user = gw_user || config[:ssh_user]
-        prompt = "Enter the password for #{user}@#{gw_host}: "
-        gw_opts.merge!(:password => prompt_for_password(prompt))
-        session.via(gw_host, user, gw_opts)
       end
 
       def configure_session
@@ -160,6 +174,31 @@ class Chef
         session_from_list(list)
       end
 
+      def get_ssh_attribute(node)
+        # Order of precedence for ssh target
+        # 1) command line attribute
+        # 2) configuration file
+        # 3) cloud attribute
+        # 4) fqdn
+        if config[:attribute]
+          Chef::Log.debug("Using node attribute '#{config[:attribute]}' as the ssh target")
+          attribute = config[:attribute]
+        elsif Chef::Config[:knife][:ssh_attribute]
+          Chef::Log.debug("Using node attribute #{Chef::Config[:knife][:ssh_attribute]}")
+          attribute = Chef::Config[:knife][:ssh_attribute]
+        elsif node[:cloud] &&
+              node[:cloud][:public_hostname] &&
+              !node[:cloud][:public_hostname].empty?
+          Chef::Log.debug("Using node attribute 'cloud[:public_hostname]' automatically as the ssh target")
+          attribute = 'cloud.public_hostname'
+        else
+          # falling back to default of fqdn
+          Chef::Log.debug("Using node attribute 'fqdn' as the ssh target")
+          attribute = "fqdn"
+        end
+        attribute
+      end
+
       def search_nodes
         list = Array.new
         query = Chef::Search::Query.new
@@ -168,25 +207,9 @@ class Chef
           # we should skip the loop to next iteration if the item
           # returned by the search is nil
           next if item.nil?
-          # if a command line attribute was not passed, and we have a
-          # cloud public_hostname, use that.  see #configure_attribute
-          # for the source of config[:attribute] and
-          # config[:attribute_from_cli]
-          if config[:attribute_from_cli]
-            Chef::Log.debug("Using node attribute '#{config[:attribute_from_cli]}' from the command line as the ssh target")
-            host = extract_nested_value(item, config[:attribute_from_cli])
-          elsif item[:cloud] &&
-                item[:cloud][:public_hostname] &&
-                !item[:cloud][:public_hostname].empty?
-            Chef::Log.debug("Using node attribute 'cloud[:public_hostname]' automatically as the ssh target")
-            host = item[:cloud][:public_hostname]
-          else
-            # ssh attribute from a configuration file or the default will land here
-            Chef::Log.debug("Using node attribute '#{config[:attribute]}' as the ssh target")
-            host = extract_nested_value(item, config[:attribute])
-          end
           # next if we couldn't find the specified attribute in the
           # returned node object
+          host = extract_nested_value(item,get_ssh_attribute(item))
           next if host.nil?
           ssh_port = item[:cloud].nil? ? nil : item[:cloud][:public_ssh_port]
           srv = [host, ssh_port]
@@ -195,32 +218,50 @@ class Chef
         list
       end
 
-      def session_from_list(list)
-        list.each do |item|
-          host, ssh_port = item
-          Chef::Log.debug("Adding #{host}")
-          session_opts = {}
-
-          ssh_config = Net::SSH.configuration_for(host)
-
+      # Net::SSH session options hash for global options. These should be
+      # options that will apply to the gateway connection in addition to the
+      # main one.
+      #
+      # @since 12.5.0
+      # @param host [String] Hostname for this session.
+      # @param port [String] SSH port for this session.
+      # @param user [String] Optional username for this session.
+      # @return [Hash<Symbol, Object>]
+      def session_options(host, port, user=nil)
+        ssh_config = Net::SSH.configuration_for(host)
+        {}.tap do |opts|
           # Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user]
-          user = config[:ssh_user] || ssh_config[:user]
-          hostspec = user ? "#{user}@#{host}" : host
-          session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file]
-          session_opts[:keys_only] = true if config[:identity_file]
-          session_opts[:password] = config[:ssh_password] if config[:ssh_password]
-          session_opts[:forward_agent] = config[:forward_agent]
-          session_opts[:port] = config[:ssh_port] ||
-                                ssh_port || # Use cloud port if available
-                                Chef::Config[:knife][:ssh_port] ||
-                                ssh_config[:port]
-          session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
-
+          opts[:user] = user || config[:ssh_user] || ssh_config[:user]
+          if config[:ssh_identity_file]
+            opts[:keys] = File.expand_path(config[:ssh_identity_file])
+            opts[:keys_only] = true
+          elsif config[:ssh_password]
+            opts[:password] = config[:ssh_password]
+          end
+          # Don't set the keys to nil if we don't have them.
+          forward_agent = config[:forward_agent] || ssh_config[:forward_agent]
+          opts[:forward_agent] = forward_agent unless forward_agent.nil?
+          port ||= ssh_config[:port]
+          opts[:port] = port unless port.nil?
+          opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
           if !config[:host_key_verify]
-            session_opts[:paranoid] = false
-            session_opts[:user_known_hosts_file] = "/dev/null"
+            opts[:paranoid] = false
+            opts[:user_known_hosts_file] = '/dev/null'
           end
+        end
+      end
 
+      def session_from_list(list)
+        list.each do |item|
+          host, ssh_port = item
+          Chef::Log.debug("Adding #{host}")
+          session_opts = session_options(host, ssh_port)
+          # Handle port overrides for the main connection.
+          session_opts[:port] = Chef::Config[:knife][:ssh_port] if Chef::Config[:knife][:ssh_port]
+          session_opts[:port] = config[:ssh_port] if config[:ssh_port]
+          # Create the hostspec.
+          hostspec = session_opts[:user] ? "#{session_opts.delete(:user)}@#{host}" : host
+          # Connect a new session on the multi.
           session.use(hostspec, session_opts)
 
           @longest = host.length if host.length > @longest
@@ -355,7 +396,7 @@ class Chef
         window = 0
         session.servers_for.each do |server|
           tf.print("screen -t \"#{server.host}\" #{window} ssh ")
-          tf.print("-i #{config[:identity_file]} ") if config[:identity_file]
+          tf.print("-i #{config[:ssh_identity_file]} ") if config[:ssh_identity_file]
           server.user ? tf.puts("#{server.user}@#{server.host}") : tf.puts(server.host)
           window += 1
         end
@@ -365,7 +406,7 @@ class Chef
 
       def tmux
         ssh_dest = lambda do |server|
-          identity = "-i #{config[:identity_file]} " if config[:identity_file]
+          identity = "-i #{config[:ssh_identity_file]} " if config[:ssh_identity_file]
           prefix = server.user ? "#{server.user}@" : ""
           "'ssh #{identity}#{prefix}#{server.host}'"
         end
@@ -373,7 +414,11 @@ class Chef
         new_window_cmds = lambda do
           if session.servers_for.size > 1
             [""] + session.servers_for[1..-1].map do |server|
-              "new-window -a -n '#{server.host}' #{ssh_dest.call(server)}"
+              if config[:tmux_split]
+                "split-window #{ssh_dest.call(server)}; tmux select-layout tiled"
+              else
+                "new-window -a -n '#{server.host}' #{ssh_dest.call(server)}"
+              end
             end
           else
             []
@@ -396,7 +441,7 @@ class Chef
         begin
           require 'appscript'
         rescue LoadError
-          STDERR.puts "you need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install"
+          STDERR.puts "You need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install"
           raise
         end
 
@@ -416,16 +461,6 @@ class Chef
         end
       end
 
-      def configure_attribute
-        # Setting 'knife[:ssh_attribute] = "foo"' in knife.rb => Chef::Config[:knife][:ssh_attribute] == 'foo'
-        # Running 'knife ssh -a foo' => both Chef::Config[:knife][:ssh_attribute] && config[:attribute] == foo
-        # Thus we can differentiate between a config file value and a command line override at this point by checking config[:attribute]
-        # We can tell here if fqdn was passed from the command line, rather than being the default, by checking config[:attribute]
-        # However, after here, we cannot tell these things, so we must preserve config[:attribute]
-        config[:attribute_from_cli] = config[:attribute]
-        config[:attribute] = (config[:attribute_from_cli] || Chef::Config[:knife][:ssh_attribute] || "fqdn").strip
-      end
-
       def cssh
         cssh_cmd = nil
         %w[csshX cssh].each do |cmd|
@@ -438,15 +473,15 @@ class Chef
         end
         raise Chef::Exceptions::Exec, "no command found for cssh" unless cssh_cmd
 
-        # pass in the consolidated itentity file option to cssh(X)
-        if config[:identity_file]
-          cssh_cmd << " --ssh_args '-i #{File.expand_path(config[:identity_file])}'"
+        # pass in the consolidated identity file option to cssh(X)
+        if config[:ssh_identity_file]
+          cssh_cmd << " --ssh_args '-i #{File.expand_path(config[:ssh_identity_file])}'"
         end
 
         session.servers_for.each do |server|
           cssh_cmd << " #{server.user ? "#{server.user}@#{server.host}" : server.host}"
         end
-        Chef::Log.debug("starting cssh session with command: #{cssh_cmd}")
+        Chef::Log.debug("Starting cssh session with command: #{cssh_cmd}")
         exec(cssh_cmd)
       end
 
@@ -485,9 +520,9 @@ class Chef
         end
       end
 
-      def configure_identity_file
-        config[:identity_file] = get_stripped_unfrozen_value(config[:identity_file] ||
-                             Chef::Config[:knife][:ssh_identity_file])
+      def configure_ssh_identity_file
+        # config[:identity_file] is DEPRECATED in favor of :ssh_identity_file
+        config[:ssh_identity_file] = get_stripped_unfrozen_value(config[:ssh_identity_file] || config[:identity_file] || Chef::Config[:knife][:ssh_identity_file])
       end
 
       def extract_nested_value(data_structure, path_spec)
@@ -499,10 +534,9 @@ class Chef
 
         @longest = 0
 
-        configure_attribute
         configure_user
         configure_password
-        configure_identity_file
+        configure_ssh_identity_file
         configure_gateway
         configure_session
 
diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb
index c5fe4fc..38b4d81 100644
--- a/lib/chef/knife/ssl_check.rb
+++ b/lib/chef/knife/ssl_check.rb
@@ -29,6 +29,8 @@ class Chef
         require 'uri'
         require 'chef/http/ssl_policies'
         require 'openssl'
+        require 'chef/mixin/proxified_socket'
+        include Chef::Mixin::ProxifiedSocket
       end
 
       banner "knife ssl check [URL] (options)"
@@ -73,11 +75,12 @@ class Chef
         exit 1
       end
 
-
       def verify_peer_socket
         @verify_peer_socket ||= begin
-          tcp_connection = TCPSocket.new(host, port)
-          OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+          tcp_connection = proxified_socket(host, port)
+          ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+          ssl_client.hostname = host
+          ssl_client
         end
       end
 
@@ -92,7 +95,7 @@ class Chef
 
       def noverify_socket
         @noverify_socket ||= begin
-          tcp_connection = TCPSocket.new(host, port)
+          tcp_connection = proxified_socket(host, port)
           OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
         end
       end
diff --git a/lib/chef/knife/ssl_fetch.rb b/lib/chef/knife/ssl_fetch.rb
index fd7d101..0c1ab7e 100644
--- a/lib/chef/knife/ssl_fetch.rb
+++ b/lib/chef/knife/ssl_fetch.rb
@@ -28,6 +28,8 @@ class Chef
         require 'socket'
         require 'uri'
         require 'openssl'
+        require 'chef/mixin/proxified_socket'
+        include Chef::Mixin::ProxifiedSocket
       end
 
       banner "knife ssl fetch [URL] (options)"
@@ -71,7 +73,7 @@ class Chef
       end
 
       def remote_cert_chain
-        tcp_connection = TCPSocket.new(host, port)
+        tcp_connection = proxified_socket(host, port)
         shady_ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
         shady_ssl_connection.connect
         shady_ssl_connection.peer_cert_chain
@@ -155,4 +157,3 @@ TRUST_TRUST
     end
   end
 end
-
diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb
index 95f2c72..1a61b03 100644
--- a/lib/chef/knife/status.rb
+++ b/lib/chef/knife/status.rb
@@ -44,7 +44,11 @@ class Chef
       option :hide_healthy,
         :short => "-H",
         :long => "--hide-healthy",
-        :description => "Hide nodes that have run chef in the last hour"
+        :description => "Hide nodes that have run chef in the last hour. [DEPRECATED] Use --hide-by-mins MINS instead"
+
+      option :hide_by_mins,
+        :long => "--hide-by-mins MINS",
+        :description => "Hide nodes that have run chef in the last MINS minutes"
 
       def append_to_query(term)
         @query << " AND " unless @query.empty?
@@ -68,12 +72,21 @@ class Chef
         append_to_query("chef_environment:#{config[:environment]}") if config[:environment]
 
         if config[:hide_healthy]
+          ui.warn("-H / --hide-healthy is deprecated. Use --hide-by-mins MINS instead")
           time = Time.now.to_i
           # AND NOT is not valid lucene syntax, so don't use append_to_query
           @query << " " unless @query.empty?
           @query << "NOT ohai_time:[#{(time - 60*60).to_s} TO #{time.to_s}]"
         end
 
+        if config[:hide_by_mins]
+          hidemins = config[:hide_by_mins].to_i
+          time = Time.now.to_i
+          # AND NOT is not valid lucene syntax, so don't use append_to_query
+          @query << " " unless @query.empty?
+          @query << "NOT ohai_time:[#{(time - hidemins*60).to_s} TO #{time.to_s}]"
+        end
+
         @query = @query.empty? ? "*:*" : @query
 
         all_nodes = []
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index 4130f06..995573c 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/user_create.rb
@@ -1,6 +1,7 @@
 #
-# Author:: Steven Danna (<steve at opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Author:: Steven Danna (<steve at chef.io>)
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2012, 2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,76 +18,134 @@
 #
 
 require 'chef/knife'
+require 'chef/knife/osc_user_create'
 
 class Chef
   class Knife
     class UserCreate < Knife
 
+      attr_accessor :user_field
+
       deps do
-        require 'chef/user'
+        require 'chef/user_v1'
         require 'chef/json_compat'
       end
 
       option :file,
         :short => "-f FILE",
         :long  => "--file FILE",
-        :description => "Write the private key to a file"
+        :description => "Write the private key to a file if the server generated one."
+
+      option :user_key,
+        :long => "--user-key FILENAME",
+        :description =>  "Set the initial default key for the user from a file on disk (cannot pass with --prevent-keygen)."
+
+      option :prevent_keygen,
+        :short => "-k",
+        :long  => "--prevent-keygen",
+        :description => "API V1 only. Prevent server from generating a default key pair for you. Cannot be passed with --user-key.",
+        :boolean => true
 
       option :admin,
         :short => "-a",
         :long  => "--admin",
-        :description => "Create the user as an admin",
+        :description => "DEPRECATED: Open Source Chef 11 only. Create the user as an admin.",
         :boolean => true
 
       option :user_password,
         :short => "-p PASSWORD",
         :long => "--password PASSWORD",
-        :description => "Password for newly created user",
+        :description => "DEPRECATED: Open Source Chef 11 only. Password for newly created user.",
         :default => ""
 
-      option :user_key,
-        :long => "--user-key FILENAME",
-        :description => "Public key for newly created user.  By default a key will be created for you."
+      banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
+
+      def user
+        @user_field ||= Chef::UserV1.new
+      end
+
+      def create_user_from_hash(hash)
+        Chef::UserV1.from_hash(hash).create
+      end
+
+      def osc_11_warning
+<<-EOF
+IF YOU ARE USING CHEF SERVER 12+, PLEASE FOLLOW THE INSTRUCTIONS UNDER knife user create --help.
+You only passed a single argument to knife user create.
+For backwards compatibility, when only a single argument is passed,
+knife user create assumes you want Open Source 11 Server user creation.
+knife user create for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user create.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+      end
 
-      banner "knife user create USER (options)"
+      def run_osc_11_user_create
+        # run osc_user_create with our input
+        ARGV.delete("user")
+        ARGV.unshift("osc_user")
+        Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+      end
 
       def run
-        @user_name = @name_args[0]
+        # DEPRECATION NOTE
+        # Remove this if statement and corrosponding code post OSC 11 support.
+        #
+        # If only 1 arg is passed, assume OSC 11 case.
+        if @name_args.length == 1
+          ui.warn(osc_11_warning)
+          run_osc_11_user_create
+        else # EC / CS 12 user create
 
-        if @user_name.nil?
-          show_usage
-          ui.fatal("You must specify a user name")
-          exit 1
-        end
+          test_mandatory_field(@name_args[0], "username")
+          user.username @name_args[0]
 
-        if config[:user_password].length == 0
-          show_usage
-          ui.fatal("You must specify a non-blank password")
-          exit 1
-        end
+          test_mandatory_field(@name_args[1], "display name")
+          user.display_name @name_args[1]
 
-        user = Chef::User.new
-        user.name(@user_name)
-        user.admin(config[:admin])
-        user.password config[:user_password]
+          test_mandatory_field(@name_args[2], "first name")
+          user.first_name @name_args[2]
 
-        if config[:user_key]
-          user.public_key File.read(File.expand_path(config[:user_key]))
-        end
+          test_mandatory_field(@name_args[3], "last name")
+          user.last_name @name_args[3]
+
+          test_mandatory_field(@name_args[4], "email")
+          user.email @name_args[4]
+
+          test_mandatory_field(@name_args[5], "password")
+          user.password @name_args[5]
+
+          if config[:user_key] && config[:prevent_keygen]
+            show_usage
+            ui.fatal("You cannot pass --user-key and --prevent-keygen")
+            exit 1
+          end
+
+          if !config[:prevent_keygen] && !config[:user_key]
+            user.create_key(true)
+          end
 
-        output = edit_data(user)
-        user = Chef::User.from_hash(output).create
+          if config[:user_key]
+            user.public_key File.read(File.expand_path(config[:user_key]))
+          end
 
-        ui.info("Created #{user}")
-        if user.private_key
-          if config[:file]
-            File.open(config[:file], "w") do |f|
-              f.print(user.private_key)
+          output = edit_data(user)
+          final_user = create_user_from_hash(output)
+
+          ui.info("Created #{user}")
+          if final_user.private_key
+            if config[:file]
+              File.open(config[:file], "w") do |f|
+                f.print(final_user.private_key)
+              end
+            else
+              ui.msg final_user.private_key
             end
-          else
-            ui.msg user.private_key
           end
         end
+
+
       end
     end
   end
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb
index b7af11b..828cd51 100644
--- a/lib/chef/knife/user_delete.rb
+++ b/lib/chef/knife/user_delete.rb
@@ -23,12 +23,46 @@ class Chef
     class UserDelete < Knife
 
       deps do
-        require 'chef/user'
+        require 'chef/user_v1'
         require 'chef/json_compat'
       end
 
       banner "knife user delete USER (options)"
 
+      def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user delete for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user delete.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+      end
+
+      def run_osc_11_user_delete
+        # run osc_user_delete with our input
+        ARGV.delete("user")
+        ARGV.unshift("osc_user")
+        Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+      end
+
+      # DEPRECATION NOTE
+      # Delete this override method after OSC 11 support is dropped
+      def delete_object(user_name)
+        confirm("Do you really want to delete #{user_name}")
+
+        if Kernel.block_given?
+          object = block.call
+        else
+          object = Chef::UserV1.load(user_name)
+          object.destroy
+        end
+
+        output(format_for_display(object)) if config[:print_after]
+        self.msg("Deleted #{user_name}")
+      end
+
       def run
         @user_name = @name_args[0]
 
@@ -38,9 +72,25 @@ class Chef
           exit 1
         end
 
-        delete_object(Chef::User, @user_name)
-      end
+        # DEPRECATION NOTE
+        #
+        # Below is modification of Chef::Knife.delete_object to detect OSC 11 server.
+        # When OSC 11 is deprecated, simply delete all this and go back to:
+        #
+        # delete_object(Chef::UserV1, @user_name)
+        #
+        # Also delete our override of delete_object above
+        object = Chef::UserV1.load(@user_name)
 
+        # OSC 11 case
+        if object.username.nil?
+          ui.warn(osc_11_warning)
+          run_osc_11_user_delete
+        else # proceed with EC / CS delete
+          delete_object(@user_name)
+        end
+
+      end
     end
   end
 end
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
index ae319c8..c3a4326 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/user_edit.rb
@@ -23,12 +23,30 @@ class Chef
     class UserEdit < Knife
 
       deps do
-        require 'chef/user'
+        require 'chef/user_v1'
         require 'chef/json_compat'
       end
 
       banner "knife user edit USER (options)"
 
+      def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user edit for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife oc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user edit.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+      end
+
+      def run_osc_11_user_edit
+        # run osc_user_create with our input
+        ARGV.delete("user")
+        ARGV.unshift("osc_user")
+        Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+      end
+
       def run
         @user_name = @name_args[0]
 
@@ -38,15 +56,26 @@ class Chef
           exit 1
         end
 
-        original_user = Chef::User.load(@user_name).to_hash
-        edited_user = edit_data(original_user)
-        if original_user != edited_user
-          user = Chef::User.from_hash(edited_user)
-          user.update
-          ui.msg("Saved #{user}.")
-        else
-          ui.msg("User unchaged, not saving.")
+        original_user = Chef::UserV1.load(@user_name).to_hash
+        # DEPRECATION NOTE
+        # Remove this if statement and corrosponding code post OSC 11 support.
+        #
+        # if username is nil, we are in the OSC 11 case,
+        # forward to deprecated command
+        if original_user["username"].nil?
+          ui.warn(osc_11_warning)
+          run_osc_11_user_edit
+        else # EC / CS 12 user create
+          edited_user = edit_data(original_user)
+          if original_user != edited_user
+            user = Chef::UserV1.from_hash(edited_user)
+            user.update
+            ui.msg("Saved #{user}.")
+          else
+            ui.msg("User unchanged, not saving.")
+          end
         end
+
       end
     end
   end
diff --git a/lib/chef/knife/user_key_create.rb b/lib/chef/knife/user_key_create.rb
new file mode 100644
index 0000000..5ed699f
--- /dev/null
+++ b/lib/chef/knife/user_key_create.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+require 'chef/knife/key_create_base'
+
+class Chef
+  class Knife
+    # Implements knife user key create using Chef::Knife::KeyCreate
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the user that this key is for
+    class UserKeyCreate < Knife
+      include Chef::Knife::KeyCreateBase
+
+      banner 'knife user key create USER (options)'
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def actor_field_name
+        'user'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyCreate.new(@actor, actor_field_name, ui, config)
+      end
+
+      def actor_missing_error
+        'You must specify a user name'
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/user_key_delete.rb b/lib/chef/knife/user_key_delete.rb
new file mode 100644
index 0000000..6db1c9f
--- /dev/null
+++ b/lib/chef/knife/user_key_delete.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+
+class Chef
+  class Knife
+    # Implements knife user key delete using Chef::Knife::KeyDelete
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the client that this key is for
+    class UserKeyDelete < Knife
+      banner "knife user key delete USER KEYNAME (options)"
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def actor_field_name
+        'user'
+      end
+
+      def actor_missing_error
+        'You must specify a user name'
+      end
+
+      def keyname_missing_error
+        'You must specify a key name'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyDelete.new(@name, @actor, actor_field_name, ui)
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+        @name = params[1]
+        if @name.nil?
+          show_usage
+          ui.fatal(keyname_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/user_key_edit.rb b/lib/chef/knife/user_key_edit.rb
new file mode 100644
index 0000000..0c35332
--- /dev/null
+++ b/lib/chef/knife/user_key_edit.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+require 'chef/knife/key_edit_base'
+
+class Chef
+  class Knife
+    # Implements knife user key edit using Chef::Knife::KeyEdit
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the user that this key is for
+    class UserKeyEdit < Knife
+      include Chef::Knife::KeyEditBase
+
+      banner 'knife user key edit USER KEYNAME (options)'
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def actor_field_name
+        'user'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyEdit.new(@name, @actor, actor_field_name, ui, config)
+      end
+
+      def actor_missing_error
+        'You must specify a user name'
+      end
+
+      def keyname_missing_error
+        'You must specify a key name'
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+        @name = params[1]
+        if @name.nil?
+          show_usage
+          ui.fatal(keyname_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/user_key_list.rb b/lib/chef/knife/user_key_list.rb
new file mode 100644
index 0000000..a73f59c
--- /dev/null
+++ b/lib/chef/knife/user_key_list.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+require 'chef/knife/key_list_base'
+
+class Chef
+  class Knife
+    # Implements knife user key list using Chef::Knife::KeyList
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the client that this key is for
+    class UserKeyList < Knife
+      include Chef::Knife::KeyListBase
+
+      banner "knife user key list USER (options)"
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def list_method
+        :list_by_user
+      end
+
+      def actor_missing_error
+        'You must specify a user name'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config)
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/user_key_show.rb b/lib/chef/knife/user_key_show.rb
new file mode 100644
index 0000000..b8453dd
--- /dev/null
+++ b/lib/chef/knife/user_key_show.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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/knife'
+
+class Chef
+  class Knife
+    # Implements knife user key show using Chef::Knife::KeyShow
+    # as a service class.
+    #
+    # @author Tyler Cloke
+    #
+    # @attr_reader [String] actor the name of the client that this key is for
+    class UserKeyShow < Knife
+      banner "knife user key show USER KEYNAME (options)"
+
+      attr_reader :actor
+
+      def initialize(argv=[])
+        super(argv)
+        @service_object = nil
+      end
+
+      def run
+        apply_params!(@name_args)
+        service_object.run
+      end
+
+      def load_method
+        :load_by_user
+      end
+
+      def actor_missing_error
+        'You must specify a user name'
+      end
+
+      def keyname_missing_error
+        'You must specify a key name'
+      end
+
+      def service_object
+        @service_object ||= Chef::Knife::KeyShow.new(@name, @actor, load_method, ui)
+      end
+
+      def apply_params!(params)
+        @actor = params[0]
+        if @actor.nil?
+          show_usage
+          ui.fatal(actor_missing_error)
+          exit 1
+        end
+        @name = params[1]
+        if @name.nil?
+          show_usage
+          ui.fatal(keyname_missing_error)
+          exit 1
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb
index 5d2e735..6a13039 100644
--- a/lib/chef/knife/user_list.rb
+++ b/lib/chef/knife/user_list.rb
@@ -18,12 +18,14 @@
 
 require 'chef/knife'
 
+# NOTE: only knife user command that is backwards compatible with OSC 11,
+# so no deprecation warnings are necessary.
 class Chef
   class Knife
     class UserList < Knife
 
       deps do
-        require 'chef/user'
+        require 'chef/user_v1'
         require 'chef/json_compat'
       end
 
@@ -35,8 +37,9 @@ class Chef
         :description => "Show corresponding URIs"
 
       def run
-        output(format_list_for_display(Chef::User.list))
+        output(format_list_for_display(Chef::UserV1.list))
       end
+
     end
   end
 end
diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb
index 946150e..09fd1cd 100644
--- a/lib/chef/knife/user_reregister.rb
+++ b/lib/chef/knife/user_reregister.rb
@@ -23,12 +23,30 @@ class Chef
     class UserReregister < Knife
 
       deps do
-        require 'chef/user'
+        require 'chef/user_v1'
         require 'chef/json_compat'
       end
 
       banner "knife user reregister USER (options)"
 
+      def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user reregister for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user reregister.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+      end
+
+      def run_osc_11_user_reregister
+        # run osc_user_edit with our input
+        ARGV.delete("user")
+        ARGV.unshift("osc_user")
+        Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+      end
+
       option :file,
         :short => "-f FILE",
         :long  => "--file FILE",
@@ -43,16 +61,29 @@ class Chef
           exit 1
         end
 
-        user = Chef::User.load(@user_name).reregister
-        Chef::Log.debug("Updated user data: #{user.inspect}")
-        key = user.private_key
-        if config[:file]
-          File.open(config[:file], "w") do |f|
-            f.print(key)
+        user = Chef::UserV1.load(@user_name)
+
+        # DEPRECATION NOTE
+        # Remove this if statement and corrosponding code post OSC 11 support.
+        #
+        # if username is nil, we are in the OSC 11 case,
+        # forward to deprecated command
+        if user.username.nil?
+          ui.warn(osc_11_warning)
+          run_osc_11_user_reregister
+        else # EC / CS 12 case
+          user.reregister
+          Chef::Log.debug("Updated user data: #{user.inspect}")
+          key = user.private_key
+          if config[:file]
+            File.open(config[:file], "w") do |f|
+              f.print(key)
+            end
+          else
+            ui.msg key
           end
-        else
-          ui.msg key
         end
+
       end
     end
   end
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb
index 61ea101..3a24434 100644
--- a/lib/chef/knife/user_show.rb
+++ b/lib/chef/knife/user_show.rb
@@ -25,12 +25,30 @@ class Chef
       include Knife::Core::MultiAttributeReturnOption
 
       deps do
-        require 'chef/user'
+        require 'chef/user_v1'
         require 'chef/json_compat'
       end
 
       banner "knife user show USER (options)"
 
+      def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user show for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user show.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+      end
+
+      def run_osc_11_user_show
+        # run osc_user_edit with our input
+        ARGV.delete("user")
+        ARGV.unshift("osc_user")
+        Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+      end
+
       def run
         @user_name = @name_args[0]
 
@@ -40,8 +58,19 @@ class Chef
           exit 1
         end
 
-        user = Chef::User.load(@user_name)
-        output(format_for_display(user))
+        user = Chef::UserV1.load(@user_name)
+
+        # DEPRECATION NOTE
+        # Remove this if statement and corrosponding code post OSC 11 support.
+        #
+        # if username is nil, we are in the OSC 11 case,
+        # forward to deprecated command
+        if user.username.nil?
+          ui.warn(osc_11_warning)
+          run_osc_11_user_show
+        else
+          output(format_for_display(user))
+        end
       end
 
     end
diff --git a/lib/chef/local_mode.rb b/lib/chef/local_mode.rb
index 79fb750..fbb72cd 100644
--- a/lib/chef/local_mode.rb
+++ b/lib/chef/local_mode.rb
@@ -15,6 +15,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 require 'chef/config'
+if Chef::Platform.windows?
+  if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.1')
+    require 'chef/monkey_patches/webrick-utils'
+  end
+end
 
 class Chef
   module LocalMode
diff --git a/lib/chef/log.rb b/lib/chef/log.rb
index 682afce..93c0cbb 100644
--- a/lib/chef/log.rb
+++ b/lib/chef/log.rb
@@ -21,6 +21,8 @@ require 'logger'
 require 'chef/monologger'
 require 'chef/exceptions'
 require 'mixlib/log'
+require 'chef/log/syslog' unless (RUBY_PLATFORM =~ /mswin|mingw|windows/)
+require 'chef/log/winevt'
 
 class Chef
   class Log
@@ -35,7 +37,25 @@ class Chef
       end
     end
 
-    def self.deprecation(msg=nil, &block)
+    #
+    # Get the location of the caller (from the recipe). Grabs the first caller
+    # that is *not* in the chef gem proper (allowing us to weed out internal
+    # calls and give the user a more useful perspective).
+    #
+    # @return [String] The location of the caller (file:line#) from caller(0..20), or nil if no non-chef caller is found.
+    #
+    def self.caller_location
+      # Pick the first caller that is *not* part of the Chef gem, that's the
+      # thing the user wrote.
+      chef_gem_path = File.expand_path("../..", __FILE__)
+      caller(0..20).select { |c| !c.start_with?(chef_gem_path) }.first
+    end
+
+    def self.deprecation(msg=nil, location=caller(2..2)[0], &block)
+      if msg
+        msg << " at #{Array(location).join("\n")}"
+        msg = msg.join("") if msg.respond_to?(:join)
+      end
       if Chef::Config[:treat_deprecation_warnings_as_errors]
         error(msg, &block)
         raise Chef::Exceptions::DeprecatedFeatureError.new(msg)
diff --git a/lib/chef/log/syslog.rb b/lib/chef/log/syslog.rb
new file mode 100644
index 0000000..0c81907
--- /dev/null
+++ b/lib/chef/log/syslog.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Lamont Granquist (<lamont at chef.io>)
+# Author:: SAWANOBORI Yukihiko (<sawanoboriyu at higanworks.com>)
+# Copyright:: Copyright (c) 2015 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 'logger'
+require 'syslog-logger'
+require 'chef/mixin/unformatter'
+
+class Chef
+  class Log
+    #
+    # Chef::Log::Syslog class.
+    # usage in client.rb:
+    #  log_location Chef::Log::Syslog.new("chef-client", ::Syslog::LOG_DAEMON)
+    #
+    class Syslog < Logger::Syslog
+      include Chef::Mixin::Unformatter
+
+      attr_accessor :sync, :formatter
+
+      def initialize(program_name = 'chef-client', facility = ::Syslog::LOG_DAEMON, logopts=nil)
+        super
+        return if defined? ::Logger::Syslog::SYSLOG
+        ::Logger::Syslog.const_set :SYSLOG, SYSLOG
+      end
+
+      def close
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/log/winevt.rb b/lib/chef/log/winevt.rb
new file mode 100644
index 0000000..c5b7c34
--- /dev/null
+++ b/lib/chef/log/winevt.rb
@@ -0,0 +1,99 @@
+#
+# Author:: Jay Mundrawala (<jdm at chef.io>)
+#
+# Copyright:: 2015, 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/event_loggers/base'
+require 'chef/platform/query_helpers'
+require 'chef/mixin/unformatter'
+
+class Chef
+  class Log
+    #
+    # Chef::Log::WinEvt class.
+    # usage in client.rb:
+    #  log_location Chef::Log::WinEvt.new
+    #
+    class WinEvt
+      # These must match those that are defined in the manifest file
+      INFO_EVENT_ID = 10100
+      WARN_EVENT_ID = 10101
+      DEBUG_EVENT_ID = 10102
+      ERROR_EVENT_ID = 10103
+      FATAL_EVENT_ID = 10104
+
+      # Since we must install the event logger, this is not really configurable
+      SOURCE = 'Chef'
+
+      include Chef::Mixin::Unformatter
+
+      attr_accessor :sync, :formatter, :level
+
+      def initialize(eventlog=nil)
+        @eventlog = eventlog || ::Win32::EventLog::open('Application')
+      end
+
+      def close
+      end
+
+      def info(msg)
+        @eventlog.report_event(
+          :event_type => ::Win32::EventLog::INFO_TYPE,
+          :source => SOURCE,
+          :event_id => INFO_EVENT_ID,
+          :data => [msg]
+        )
+      end
+
+      def warn(msg)
+        @eventlog.report_event(
+          :event_type => ::Win32::EventLog::WARN_TYPE,
+          :source => SOURCE,
+          :event_id => WARN_EVENT_ID,
+          :data => [msg]
+        )
+      end
+
+      def debug(msg)
+        @eventlog.report_event(
+          :event_type => ::Win32::EventLog::INFO_TYPE,
+          :source => SOURCE,
+          :event_id => DEBUG_EVENT_ID,
+          :data => [msg]
+        )
+      end
+
+      def error(msg)
+        @eventlog.report_event(
+          :event_type => ::Win32::EventLog::ERROR_TYPE,
+          :source => SOURCE,
+          :event_id => ERROR_EVENT_ID,
+          :data => [msg]
+        )
+      end
+
+      def fatal(msg)
+        @eventlog.report_event(
+          :event_type => ::Win32::EventLog::ERROR_TYPE,
+          :source => SOURCE,
+          :event_id => FATAL_EVENT_ID,
+          :data => [msg]
+        )
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/mixin/api_version_request_handling.rb b/lib/chef/mixin/api_version_request_handling.rb
new file mode 100644
index 0000000..20ab3bf
--- /dev/null
+++ b/lib/chef/mixin/api_version_request_handling.rb
@@ -0,0 +1,66 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright 2015 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.
+#
+
+class Chef
+  module Mixin
+    module ApiVersionRequestHandling
+      # Input:
+      # exeception:
+      #   Net::HTTPServerException that may or may not contain the x-ops-server-api-version header
+      # supported_client_versions:
+      #  An array of Integers that represent the API versions the client supports.
+      #
+      # Output:
+      # nil:
+      #  If the execption was not a 406 or the server does not support versioning
+      # Array of length zero:
+      #  If there was no intersection between supported client versions and supported server versions
+      # Arrary of Integers:
+      #  If there was an intersection of supported versions, the array returns will contain that intersection
+      def server_client_api_version_intersection(exception, supported_client_versions)
+        # return empty array unless 406 Unacceptable with proper header
+        return nil if exception.response.code != "406" || exception.response["x-ops-server-api-version"].nil?
+
+        # intersection of versions the server and client support, will be of length zero if no intersection
+        server_supported_client_versions = Array.new
+
+        header = Chef::JSONCompat.from_json(exception.response["x-ops-server-api-version"])
+        min_server_version = Integer(header["min_version"])
+        max_server_version = Integer(header["max_version"])
+
+        supported_client_versions.each do |version|
+          if version >= min_server_version && version <= max_server_version
+            server_supported_client_versions.push(version)
+          end
+        end
+        server_supported_client_versions
+      end
+
+      def reregister_only_v0_supported_error_msg(max_version, min_version)
+        <<-EOH
+The reregister command only supports server API version 0.
+The server that received the request supports a min version of #{min_version} and a max version of #{max_version}.
+User keys are now managed via the key rotation commmands.
+Please refer to the documentation on how to manage your keys via the key rotation commands:
+https://docs.chef.io/server_security.html#key-rotation
+EOH
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/mixin/convert_to_class_name.rb b/lib/chef/mixin/convert_to_class_name.rb
index 19f229f..14676e5 100644
--- a/lib/chef/mixin/convert_to_class_name.rb
+++ b/lib/chef/mixin/convert_to_class_name.rb
@@ -23,9 +23,7 @@ class Chef
       extend self
 
       def convert_to_class_name(str)
-        str = str.dup
-        str.gsub!(/[^A-Za-z0-9_]/,'_')
-        str.gsub!(/^(_+)?/,'')
+        str = normalize_snake_case_name(str)
         rname = nil
         regexp = %r{^(.+?)(_(.+))?$}
 
@@ -51,6 +49,13 @@ class Chef
         str
       end
 
+      def normalize_snake_case_name(str)
+        str = str.dup
+        str.gsub!(/[^A-Za-z0-9_]/,'_')
+        str.gsub!(/^(_+)?/,'')
+        str
+      end
+
       def snake_case_basename(str)
         with_namespace = convert_to_snake_case(str)
         with_namespace.split("::").last.sub(/^_/, '')
@@ -58,7 +63,8 @@ class Chef
 
       def filename_to_qualified_string(base, filename)
         file_base = File.basename(filename, ".rb")
-        base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
+        str = base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
+        normalize_snake_case_name(str)
       end
 
       # Copied from rails activesupport.  In ruby >= 2.0 const_get will just do this, so this can
diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb
index 489f27c..562af54 100644
--- a/lib/chef/mixin/deprecation.rb
+++ b/lib/chef/mixin/deprecation.rb
@@ -95,6 +95,30 @@ class Chef
         DeprecatedInstanceVariable.new(obj, name, level)
       end
 
+      def deprecated_attr(name, alternative)
+        deprecated_attr_reader(name, alternative)
+        deprecated_attr_writer(name, alternative)
+      end
+
+      def deprecated_attr_reader(name, alternative, level=:warn)
+        define_method(name) do
+          Chef.log_deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.")
+          Chef.log_deprecation(alternative)
+          Chef.log_deprecation("Called from:")
+          caller[0..3].each {|c| Chef.log_deprecation(c)}
+          instance_variable_get("@#{name}")
+        end
+      end
+
+      def deprecated_attr_writer(name, alternative, level=:warn)
+        define_method("#{name}=") do |value|
+          Chef.log_deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.")
+          Chef.log_deprecation(alternative)
+          Chef.log_deprecation("Called from:")
+          caller[0..3].each {|c| Chef.log_deprecation(c)}
+          instance_variable_set("@#{name}", value)
+        end
+      end
     end
   end
 end
diff --git a/lib/chef/mixin/get_source_from_package.rb b/lib/chef/mixin/get_source_from_package.rb
index 2ed2518..cb5583b 100644
--- a/lib/chef/mixin/get_source_from_package.rb
+++ b/lib/chef/mixin/get_source_from_package.rb
@@ -1,5 +1,5 @@
 # Author:: Lamont Granquist (<lamont at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,6 +27,12 @@
 class Chef
   module Mixin
     module GetSourceFromPackage
+      # FIXME:  this is some bad code that I wrote a long time ago.
+      #  - it does too much in the initializer
+      #  - it mutates the new_resource
+      #  - it does not support multipackage arrays
+      # this code is deprecated, check out the :use_package_names_for_source
+      # subclass directive instead
       def initialize(new_resource, run_context)
         super
         return if new_resource.package_name.is_a?(Array)
@@ -40,4 +46,3 @@ class Chef
     end
   end
 end
-
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index 78d72dc..e3c7657 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -15,9 +15,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+require 'chef/constants'
+require 'chef/property'
+require 'chef/delayed_evaluator'
+
 class Chef
-  class DelayedEvaluator < Proc
-  end
   module Mixin
     module ParamsValidate
 
@@ -32,20 +34,55 @@ class Chef
       # Would raise an exception if the value of :one above is not a kind_of? string.  Valid
       # map options are:
       #
-      # :default:: Sets the default value for this parameter.
-      # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
-      #              The key will be inserted into the error message if the Proc does not return true:
-      #                 "Option #{key}'s value #{value} #{message}!"
-      # :kind_of:: Ensure that the value is a kind_of?(Whatever).  If passed an array, it will ensure
-      #            that the value is one of those types.
-      # :respond_to:: Ensure that the value has a given method.  Takes one method name or an array of
-      #               method names.
-      # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
-      #             by default, options are not required.
-      # :regex:: Match the value of the parameter against a regular expression.
-      # :equal_to:: Match the value of the parameter with ==.  An array means it can be equal to any
-      #             of the values.
+      # @param opts [Hash<Symbol,Object>] Validation opts.
+      #   @option opts [Object,Array] :is An object, or list of
+      #     objects, that must match the value using Ruby's `===` operator
+      #     (`opts[:is].any? { |v| v === value }`). (See #_pv_is.)
+      #   @option opts [Object,Array] :equal_to An object, or list
+      #     of objects, that must be equal to the value using Ruby's `==`
+      #     operator (`opts[:is].any? { |v| v == value }`)  (See #_pv_equal_to.)
+      #   @option opts [Regexp,Array<Regexp>] :regex An object, or
+      #     list of objects, that must match the value with `regex.match(value)`.
+      #     (See #_pv_regex)
+      #   @option opts [Class,Array<Class>] :kind_of A class, or
+      #     list of classes, that the value must be an instance of.  (See
+      #     #_pv_kind_of.)
+      #   @option opts [Hash<String,Proc>] :callbacks A hash of
+      #     messages -> procs, all of which match the value. The proc must
+      #     return a truthy or falsey value (true means it matches).  (See
+      #     #_pv_callbacks.)
+      #   @option opts [Symbol,Array<Symbol>] :respond_to A method
+      #     name, or list of method names, the value must respond to.  (See
+      #     #_pv_respond_to.)
+      #   @option opts [Symbol,Array<Symbol>] :cannot_be A property,
+      #     or a list of properties, that the value cannot have (such as `:nil` or
+      #     `:empty`). The method with a questionmark at the end is called on the
+      #     value (e.g. `value.empty?`). If the value does not have this method,
+      #     it is considered valid (i.e. if you don't respond to `empty?` we
+      #     assume you are not empty).  (See #_pv_cannot_be.)
+      #   @option opts [Proc] :coerce A proc which will be called to
+      #     transform the user input to canonical form. The value is passed in,
+      #     and the transformed value returned as output. Lazy values will *not*
+      #     be passed to this method until after they are evaluated. Called in the
+      #     context of the resource (meaning you can access other properties).
+      #     (See #_pv_coerce.) (See #_pv_coerce.)
+      #   @option opts [Boolean] :required `true` if this property
+      #     must be present and not `nil`; `false` otherwise. This is checked
+      #     after the resource is fully initialized. (See #_pv_required.)
+      #   @option opts [Boolean] :name_property `true` if this
+      #     property defaults to the same value as `name`. Equivalent to
+      #     `default: lazy { name }`, except that #property_is_set? will
+      #     return `true` if the property is set *or* if `name` is set. (See
+      #     #_pv_name_property.)
+      #   @option opts [Boolean] :name_attribute Same as `name_property`.
+      #   @option opts [Object] :default The value this property
+      #     will return if the user does not set one. If this is `lazy`, it will
+      #     be run in the context of the instance (and able to access other
+      #     properties).  (See #_pv_default.)
+      #
       def validate(opts, map)
+        map = map.validation_options if map.is_a?(Property)
+
         #--
         # validate works by taking the keys in the validation map, assuming it's a hash, and
         # looking for _pv_:symbol as methods.  Assuming it find them, it calls the right
@@ -65,7 +102,7 @@ class Chef
             true
           when Hash
             validation.each do |check, carg|
-              check_method = "_pv_#{check.to_s}"
+              check_method = "_pv_#{check}"
               if self.respond_to?(check_method, true)
                 self.send(check_method, opts, key, carg)
               else
@@ -81,162 +118,352 @@ class Chef
         DelayedEvaluator.new(&block)
       end
 
-      def set_or_return(symbol, arg, validation)
-        iv_symbol = "@#{symbol.to_s}".to_sym
-        if arg == nil && self.instance_variable_defined?(iv_symbol) == true
-          ivar = self.instance_variable_get(iv_symbol)
-          if(ivar.is_a?(DelayedEvaluator))
-            validate({ symbol => ivar.call }, { symbol => validation })[symbol]
-          else
-            ivar
-          end
-        else
-          if(arg.is_a?(DelayedEvaluator))
-            val = arg
-          else
-            val = validate({ symbol => arg }, { symbol => validation })[symbol]
+      def set_or_return(symbol, value, validation)
+        property = SetOrReturnProperty.new(name: symbol, **validation)
+        property.call(self, value)
+      end
 
-            # Handle the case where the "default" was a DelayedEvaluator. In
-            # this case, the block yields an optional parameter of +self+,
-            # which is the equivalent of "new_resource"
-            if val.is_a?(DelayedEvaluator)
-              val = val.call(self)
-            end
-          end
-          self.instance_variable_set(iv_symbol, val)
+      private
+
+      def explicitly_allows_nil?(key, validation)
+        validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false)
+      end
+
+      # Return the value of a parameter, or nil if it doesn't exist.
+      def _pv_opts_lookup(opts, key)
+        if opts.has_key?(key.to_s)
+          opts[key.to_s]
+        elsif opts.has_key?(key.to_sym)
+          opts[key.to_sym]
+        else
+          nil
         end
       end
 
-      private
+      # Raise an exception if the parameter is not found.
+      def _pv_required(opts, key, is_required=true, explicitly_allows_nil=false)
+        if is_required
+          return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?)
+          return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?)
+          raise Exceptions::ValidationFailed, "Required argument #{key.inspect} is missing!"
+        end
+        true
+      end
 
-        # Return the value of a parameter, or nil if it doesn't exist.
-        def _pv_opts_lookup(opts, key)
-          if opts.has_key?(key.to_s)
-            opts[key.to_s]
-          elsif opts.has_key?(key.to_sym)
-            opts[key.to_sym]
-          else
-            nil
+      #
+      # List of things values must be equal to.
+      #
+      # Uses Ruby's `==` to evaluate (equal_to == value).  At least one must
+      # match for the value to be valid.
+      #
+      # `nil` passes this validation automatically.
+      #
+      # @return [Array,nil] List of things values must be equal to, or nil if
+      #   equal_to is unspecified.
+      #
+      def _pv_equal_to(opts, key, to_be)
+        value = _pv_opts_lookup(opts, key)
+        unless value.nil?
+          to_be = Array(to_be)
+          to_be.each do |tb|
+            return true if value == tb
           end
+          raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}!  You passed #{value.inspect}."
         end
+      end
 
-        # Raise an exception if the parameter is not found.
-        def _pv_required(opts, key, is_required=true)
-          if is_required
-            if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) ||
-                (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?)
-              true
-            else
-              raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
-            end
+      #
+      # List of things values must be instances of.
+      #
+      # Uses value.kind_of?(kind_of) to evaluate. At least one must match for
+      # the value to be valid.
+      #
+      # `nil` automatically passes this validation.
+      #
+      def _pv_kind_of(opts, key, to_be)
+        value = _pv_opts_lookup(opts, key)
+        unless value.nil?
+          to_be = Array(to_be)
+          to_be.each do |tb|
+            return true if value.kind_of?(tb)
           end
+          raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}!  You passed #{value.inspect}."
         end
+      end
 
-        def _pv_equal_to(opts, key, to_be)
-          value = _pv_opts_lookup(opts, key)
-          unless value.nil?
-            passes = false
-            Array(to_be).each do |tb|
-              passes = true if value == tb
-            end
-            unless passes
-              raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}!  You passed #{value.inspect}."
+      #
+      # List of method names values must respond to.
+      #
+      # Uses value.respond_to?(respond_to) to evaluate. At least one must match
+      # for the value to be valid.
+      #
+      def _pv_respond_to(opts, key, method_name_list)
+        value = _pv_opts_lookup(opts, key)
+        unless value.nil?
+          Array(method_name_list).each do |method_name|
+            unless value.respond_to?(method_name)
+              raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
             end
           end
         end
+      end
 
-        # Raise an exception if the parameter is not a kind_of?(to_be)
-        def _pv_kind_of(opts, key, to_be)
-          value = _pv_opts_lookup(opts, key)
-          unless value.nil?
-            passes = false
-            Array(to_be).each do |tb|
-              passes = true if value.kind_of?(tb)
-            end
-            unless passes
-              raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}!  You passed #{value.inspect}."
+      #
+      # List of things that must not be true about the value.
+      #
+      # Calls `value.<thing>?` All responses must be false for the value to be
+      # valid.
+      # Values which do not respond to <thing>? are considered valid (because if
+      # a value doesn't respond to `:readable?`, then it probably isn't
+      # readable.)
+      #
+      # @example
+      #   ```ruby
+      #   property :x, cannot_be: [ :nil, :empty ]
+      #   x [ 1, 2 ] #=> valid
+      #   x 1        #=> valid
+      #   x []       #=> invalid
+      #   x nil      #=> invalid
+      #   ```
+      #
+      def _pv_cannot_be(opts, key, predicate_method_base_name)
+        value = _pv_opts_lookup(opts, key)
+        if !value.nil?
+          Array(predicate_method_base_name).each do |method_name|
+            predicate_method = :"#{method_name}?"
+
+            if value.respond_to?(predicate_method)
+              if value.send(predicate_method)
+                raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+              end
             end
           end
         end
+      end
 
-        # Raise an exception if the parameter does not respond to a given set of methods.
-        def _pv_respond_to(opts, key, method_name_list)
-          value = _pv_opts_lookup(opts, key)
-          unless value.nil?
-            Array(method_name_list).each do |method_name|
-              unless value.respond_to?(method_name)
-                raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
-              end
-            end
+      #
+      # The default value for a property.
+      #
+      # When the property is not assigned, this will be used.
+      #
+      # If this is a lazy value, it will either be passed the resource as a value,
+      # or if the lazy proc does not take parameters, it will be run in the
+      # context of the instance with instance_eval.
+      #
+      # @example
+      #   ```ruby
+      #   property :x, default: 10
+      #   ```
+      #
+      # @example
+      #   ```ruby
+      #   property :x
+      #   property :y, default: lazy { x+2 }
+      #   ```
+      #
+      # @example
+      #   ```ruby
+      #   property :x
+      #   property :y, default: lazy { |r| r.x+2 }
+      #   ```
+      #
+      def _pv_default(opts, key, default_value)
+        value = _pv_opts_lookup(opts, key)
+        if value.nil?
+          default_value = default_value.freeze if !default_value.is_a?(DelayedEvaluator)
+          opts[key] = default_value
+        end
+      end
+
+      #
+      # List of regexes values that must match.
+      #
+      # Uses regex.match() to evaluate. At least one must match for the value to
+      # be valid.
+      #
+      # `nil` passes regex validation automatically.
+      #
+      # @example
+      #   ```ruby
+      #   property :x, regex: [ /abc/, /xyz/ ]
+      #   ```
+      #
+      def _pv_regex(opts, key, regex)
+        value = _pv_opts_lookup(opts, key)
+        if !value.nil?
+          Array(regex).flatten.each do |r|
+            return true if r.match(value.to_s)
           end
+          raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
         end
+      end
 
-        # Assert that parameter returns false when passed a predicate method.
-        # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
-        # error value.blank? returns a 'truthy' (not nil or false) value.
-        #
-        # Note, this will *PASS* if the object doesn't respond to the method.
-        # So, to make sure a value is not nil and not blank, you need to do
-        # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
-        def _pv_cannot_be(opts, key, predicate_method_base_name)
-          value = _pv_opts_lookup(opts, key)
-          predicate_method = (predicate_method_base_name.to_s + "?").to_sym
-
-          if value.respond_to?(predicate_method)
-            if value.send(predicate_method)
-              raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+      #
+      # List of procs we pass the value to.
+      #
+      # All procs must return true for the value to be valid. If any procs do
+      # not return true, the key will be used for the message: `"Property x's
+      # value :y <message>"`.
+      #
+      # @example
+      #   ```ruby
+      #   property :x, callbacks: { "is bigger than 10" => proc { |v| v <= 10 }, "is not awesome" => proc { |v| !v.awesome }}
+      #   ```
+      #
+      def _pv_callbacks(opts, key, callbacks)
+        raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+        value = _pv_opts_lookup(opts, key)
+        if !value.nil?
+          callbacks.each do |message, zeproc|
+            unless zeproc.call(value)
+              raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
             end
           end
         end
+      end
 
-        # Assign a default value to a parameter.
-        def _pv_default(opts, key, default_value)
-          value = _pv_opts_lookup(opts, key)
-          if value == nil
-            opts[key] = default_value
+      #
+      # Allows a parameter to default to the value of the resource name.
+      #
+      # @example
+      #   ```ruby
+      #    property :x, name_property: true
+      #   ```
+      #
+      def _pv_name_property(opts, key, is_name_property=true)
+        if is_name_property
+          if opts[key].nil?
+            opts[key] = self.instance_variable_get(:"@name")
           end
         end
+      end
+      alias :_pv_name_attribute :_pv_name_property
 
-        # Check a parameter against a regular expression.
-        def _pv_regex(opts, key, regex)
-          value = _pv_opts_lookup(opts, key)
-          if value != nil
-            passes = false
-            [ regex ].flatten.each do |r|
-              if value != nil
-                if r.match(value.to_s)
-                  passes = true
-                end
-              end
-            end
-            unless passes
-              raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
-            end
+      #
+      # List of valid things values can be.
+      #
+      # Uses Ruby's `===` to evaluate (is === value).  At least one must match
+      # for the value to be valid.
+      #
+      # If a proc is passed, it is instance_eval'd in the resource, passed the
+      # value, and must return a truthy or falsey value.
+      #
+      # @example Class
+      #   ```ruby
+      #   property :x, String
+      #   x 'valid' #=> valid
+      #   x 1       #=> invalid
+      #   x nil     #=> invalid
+      #
+      # @example Value
+      #   ```ruby
+      #   property :x, [ :a, :b, :c, nil ]
+      #   x :a  #=> valid
+      #   x nil #=> valid
+      #   ```
+      #
+      # @example Regex
+      #   ```ruby
+      #   property :x, /bar/
+      #   x 'foobar' #=> valid
+      #   x 'foo'    #=> invalid
+      #   x nil      #=> invalid
+      #   ```
+      #
+      # @example Proc
+      #   ```ruby
+      #   property :x, proc { |x| x > y }
+      #   property :y, default: 2
+      #   x 3 #=> valid
+      #   x 1 #=> invalid
+      #   ```
+      #
+      # @example Property
+      #   ```ruby
+      #   type = Property.new(is: String)
+      #   property :x, type
+      #   x 'foo' #=> valid
+      #   x 1     #=> invalid
+      #   x nil   #=> invalid
+      #   ```
+      #
+      # @example RSpec Matcher
+      #   ```ruby
+      #   include RSpec::Matchers
+      #   property :x, a_string_matching /bar/
+      #   x 'foobar' #=> valid
+      #   x 'foo'    #=> invalid
+      #   x nil      #=> invalid
+      #   ```
+      #
+      def _pv_is(opts, key, to_be, raise_error: true)
+        return true if !opts.has_key?(key.to_s) && !opts.has_key?(key.to_sym)
+        value = _pv_opts_lookup(opts, key)
+        to_be = [ to_be ].flatten(1)
+        to_be.each do |tb|
+          case tb
+          when Proc
+            return true if instance_exec(value, &tb)
+          when Property
+            validate(opts, { key => tb.validation_options })
+            return true
+          else
+            return true if tb === value
           end
         end
 
-        # Check a parameter against a hash of proc's.
-        def _pv_callbacks(opts, key, callbacks)
-          raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
-          value = _pv_opts_lookup(opts, key)
-          if value != nil
-            callbacks.each do |message, zeproc|
-              if zeproc.call(value) != true
-                raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
-              end
-            end
+        if raise_error
+          raise Exceptions::ValidationFailed, "Option #{key} must be one of: #{to_be.join(", ")}!  You passed #{value.inspect}."
+        else
+          false
+        end
+      end
+
+      #
+      # Method to mess with a value before it is validated and stored.
+      #
+      # Allows you to transform values into a canonical form that is easy to
+      # work with.
+      #
+      # This is passed the value to transform, and is run in the context of the
+      # instance (so it has access to other resource properties). It must return
+      # the value that will be stored in the instance.
+      #
+      # @example
+      #   ```ruby
+      #   property :x, Integer, coerce: { |v| v.to_i }
+      #   ```
+      #
+      def _pv_coerce(opts, key, coercer)
+        if opts.has_key?(key.to_s)
+          opts[key.to_s] = instance_exec(opts[key], &coercer)
+        elsif opts.has_key?(key.to_sym)
+          opts[key.to_sym] = instance_exec(opts[key], &coercer)
+        end
+      end
+
+      # Used by #set_or_return to avoid emitting a deprecation warning for
+      # "value nil" and to keep default stickiness working exactly the same
+      # @api private
+      class SetOrReturnProperty < Chef::Property
+        def get(resource)
+          value = super
+          # All values are sticky, frozen or not
+          if !is_set?(resource)
+            set_value(resource, value)
           end
+          value
         end
 
-        # Allow a parameter to default to @name
-        def _pv_name_attribute(opts, key, is_name_attribute=true)
-          if is_name_attribute
-            if opts[key] == nil
-              opts[key] = self.instance_variable_get("@name")
-            end
+        def call(resource, value=NOT_PASSED)
+          # setting to nil does a get
+          if value.nil? && !explicitly_accepts_nil?(resource)
+            get(resource)
+          else
+            super
           end
         end
+      end
     end
   end
 end
-
diff --git a/lib/chef/mixin/powershell_out.rb b/lib/chef/mixin/powershell_out.rb
new file mode 100644
index 0000000..e4f29c0
--- /dev/null
+++ b/lib/chef/mixin/powershell_out.rb
@@ -0,0 +1,98 @@
+#--
+# Copyright:: Copyright (c) 2015 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/mixin/shell_out'
+require 'chef/mixin/windows_architecture_helper'
+
+class Chef
+  module Mixin
+    module PowershellOut
+      include Chef::Mixin::ShellOut
+      include Chef::Mixin::WindowsArchitectureHelper
+
+      # Run a command under powershell with the same API as shell_out.  The
+      # options hash is extended to take an "architecture" flag which
+      # can be set to :i386 or :x86_64 to force the windows architecture.
+      #
+      # @param script [String] script to run
+      # @param options [Hash] options hash
+      # @return [Mixlib::Shellout] mixlib-shellout object
+      def powershell_out(*command_args)
+        script = command_args.first
+        options = command_args.last.is_a?(Hash) ? command_args.last : nil
+
+        run_command_with_os_architecture(script, options)
+      end
+
+      # Run a command under powershell with the same API as shell_out!
+      # (raises exceptions on errors)
+      #
+      # @param script [String] script to run
+      # @param options [Hash] options hash
+      # @return [Mixlib::Shellout] mixlib-shellout object
+      def powershell_out!(*command_args)
+        cmd = powershell_out(*command_args)
+        cmd.error!
+        cmd
+      end
+
+      private
+
+      # Helper function to run shell_out and wrap it with the correct
+      # flags to possibly disable WOW64 redirection (which we often need
+      # because chef-client runs as a 32-bit app on 64-bit windows).
+      #
+      # @param script [String] script to run
+      # @param options [Hash] options hash
+      # @return [Mixlib::Shellout] mixlib-shellout object
+      def run_command_with_os_architecture(script, options)
+        options ||= {}
+        options = options.dup
+        arch = options.delete(:architecture)
+
+        with_os_architecture(nil, architecture: arch) do
+          shell_out(
+            build_powershell_command(script),
+            options
+          )
+        end
+      end
+
+      # Helper to build a powershell command around the script to run.
+      #
+      # @param script [String] script to run
+      # @retrurn [String] powershell command to execute
+      def build_powershell_command(script)
+        flags = [
+          # Hides the copyright banner at startup.
+          "-NoLogo",
+          # Does not present an interactive prompt to the user.
+          "-NonInteractive",
+          # Does not load the Windows PowerShell profile.
+          "-NoProfile",
+          # always set the ExecutionPolicy flag
+          # see http://technet.microsoft.com/en-us/library/ee176961.aspx
+          "-ExecutionPolicy Unrestricted",
+          # Powershell will hang if STDIN is redirected
+          # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
+          "-InputFormat None"
+        ]
+
+        "powershell.exe #{flags.join(' ')} -Command \"#{script}\""
+      end
+    end
+  end
+end
diff --git a/lib/chef/mixin/properties.rb b/lib/chef/mixin/properties.rb
new file mode 100644
index 0000000..85abe44
--- /dev/null
+++ b/lib/chef/mixin/properties.rb
@@ -0,0 +1,302 @@
+require 'chef/delayed_evaluator'
+require 'chef/mixin/params_validate'
+require 'chef/property'
+
+class Chef
+  module Mixin
+    module Properties
+      module ClassMethods
+        #
+        # The list of properties defined on this resource.
+        #
+        # Everything defined with `property` is in this list.
+        #
+        # @param include_superclass [Boolean] `true` to include properties defined
+        #   on superclasses; `false` or `nil` to return the list of properties
+        #   directly on this class.
+        #
+        # @return [Hash<Symbol,Property>] The list of property names and types.
+        #
+        def properties(include_superclass=true)
+          if include_superclass
+            result = {}
+            ancestors.reverse_each { |c| result.merge!(c.properties(false)) if c.respond_to?(:properties) }
+            result
+          else
+            @properties ||= {}
+          end
+        end
+
+        #
+        # Create a property on this resource class.
+        #
+        # If a superclass has this property, or if this property has already been
+        # defined by this resource, this will *override* the previous value.
+        #
+        # @param name [Symbol] The name of the property.
+        # @param type [Object,Array<Object>] The type(s) of this property.
+        #   If present, this is prepended to the `is` validation option.
+        # @param options [Hash<Symbol,Object>] Validation options.
+        #   @option options [Object,Array] :is An object, or list of
+        #     objects, that must match the value using Ruby's `===` operator
+        #     (`options[:is].any? { |v| v === value }`).
+        #   @option options [Object,Array] :equal_to An object, or list
+        #     of objects, that must be equal to the value using Ruby's `==`
+        #     operator (`options[:is].any? { |v| v == value }`)
+        #   @option options [Regexp,Array<Regexp>] :regex An object, or
+        #     list of objects, that must match the value with `regex.match(value)`.
+        #   @option options [Class,Array<Class>] :kind_of A class, or
+        #     list of classes, that the value must be an instance of.
+        #   @option options [Hash<String,Proc>] :callbacks A hash of
+        #     messages -> procs, all of which match the value. The proc must
+        #     return a truthy or falsey value (true means it matches).
+        #   @option options [Symbol,Array<Symbol>] :respond_to A method
+        #     name, or list of method names, the value must respond to.
+        #   @option options [Symbol,Array<Symbol>] :cannot_be A property,
+        #     or a list of properties, that the value cannot have (such as `:nil` or
+        #     `:empty`). The method with a questionmark at the end is called on the
+        #     value (e.g. `value.empty?`). If the value does not have this method,
+        #     it is considered valid (i.e. if you don't respond to `empty?` we
+        #     assume you are not empty).
+        #   @option options [Proc] :coerce A proc which will be called to
+        #     transform the user input to canonical form. The value is passed in,
+        #     and the transformed value returned as output. Lazy values will *not*
+        #     be passed to this method until after they are evaluated. Called in the
+        #     context of the resource (meaning you can access other properties).
+        #   @option options [Boolean] :required `true` if this property
+        #     must be present; `false` otherwise. This is checked after the resource
+        #     is fully initialized.
+        #   @option options [Boolean] :name_property `true` if this
+        #     property defaults to the same value as `name`. Equivalent to
+        #     `default: lazy { name }`, except that #property_is_set? will
+        #     return `true` if the property is set *or* if `name` is set.
+        #   @option options [Boolean] :name_attribute Same as `name_property`.
+        #   @option options [Object] :default The value this property
+        #     will return if the user does not set one. If this is `lazy`, it will
+        #     be run in the context of the instance (and able to access other
+        #     properties).
+        #   @option options [Boolean] :desired_state `true` if this property is
+        #     part of desired state. Defaults to `true`.
+        #   @option options [Boolean] :identity `true` if this property
+        #     is part of object identity. Defaults to `false`.
+        #
+        # @example Bare property
+        #   property :x
+        #
+        # @example With just a type
+        #   property :x, String
+        #
+        # @example With just options
+        #   property :x, default: 'hi'
+        #
+        # @example With type and options
+        #   property :x, String, default: 'hi'
+        #
+        def property(name, type=NOT_PASSED, **options)
+          name = name.to_sym
+
+          options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) }
+
+          options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name)
+          options.merge!(name: name, declared_in: self)
+
+          if type == NOT_PASSED
+            # If a type is not passed, the property derives from the
+            # superclass property (if any)
+            if properties.has_key?(name)
+              property = properties[name].derive(**options)
+            else
+              property = property_type(**options)
+            end
+
+          # If a Property is specified, derive a new one from that.
+          elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property)
+            property = type.derive(**options)
+
+          # If a primitive type was passed, combine it with "is"
+          else
+            if options[:is]
+              options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
+            else
+              options[:is] = type
+            end
+            property = property_type(**options)
+          end
+
+          local_properties = properties(false)
+          local_properties[name] = property
+
+          property.emit_dsl
+        end
+
+        #
+        # Create a reusable property type that can be used in multiple properties
+        # in different resources.
+        #
+        # @param options [Hash<Symbol,Object>] Validation options. see #property for
+        #   the list of options.
+        #
+        # @example
+        #   property_type(default: 'hi')
+        #
+        def property_type(**options)
+          Property.derive(**options)
+        end
+
+        #
+        # Create a lazy value for assignment to a default value.
+        #
+        # @param block The block to run when the value is retrieved.
+        #
+        # @return [Chef::DelayedEvaluator] The lazy value
+        #
+        def lazy(&block)
+          DelayedEvaluator.new(&block)
+        end
+
+        #
+        # Get or set the list of desired state properties for this resource.
+        #
+        # State properties are properties that describe the desired state
+        # of the system, such as file permissions or ownership.
+        # In general, state properties are properties that could be populated by
+        # examining the state of the system (e.g., File.stat can tell you the
+        # permissions on an existing file). Contrarily, properties that are not
+        # "state properties" usually modify the way Chef itself behaves, for example
+        # by providing additional options for a package manager to use when
+        # installing a package.
+        #
+        # This list is used by the Chef client auditing system to extract
+        # information from resources to describe changes made to the system.
+        #
+        # This method is unnecessary when declaring properties with `property`;
+        # properties are added to state_properties by default, and can be turned off
+        # with `desired_state: false`.
+        #
+        # ```ruby
+        # property :x # part of desired state
+        # property :y, desired_state: false # not part of desired state
+        # ```
+        #
+        # @param names [Array<Symbol>] A list of property names to set as desired
+        #   state.
+        #
+        # @return [Array<Property>] All properties in desired state.
+        #
+        def state_properties(*names)
+          if !names.empty?
+            names = names.map { |name| name.to_sym }.uniq
+
+            local_properties = properties(false)
+            # Add new properties to the list.
+            names.each do |name|
+              property = properties[name]
+              if !property
+                self.property name, instance_variable_name: false, desired_state: true
+              elsif !property.desired_state?
+                self.property name, desired_state: true
+              end
+            end
+
+            # If state_attrs *excludes* something which is currently desired state,
+            # mark it as desired_state: false.
+            local_properties.each do |name,property|
+              if property.desired_state? && !names.include?(name)
+                self.property name, desired_state: false
+              end
+            end
+          end
+
+          properties.values.select { |property| property.desired_state? }
+        end
+
+        #
+        # Set the identity of this resource to a particular set of properties.
+        #
+        # This drives #identity, which returns data that uniquely refers to a given
+        # resource on the given node (in such a way that it can be correlated
+        # across Chef runs).
+        #
+        # This method is unnecessary when declaring properties with `property`;
+        # properties can be added to identity during declaration with
+        # `identity: true`.
+        #
+        # ```ruby
+        # property :x, identity: true # part of identity
+        # property :y # not part of identity
+        # ```
+        #
+        # If no properties are marked as identity, "name" is considered the identity.
+        #
+        # @param names [Array<Symbol>] A list of property names to set as the identity.
+        #
+        # @return [Array<Property>] All identity properties.
+        #
+        def identity_properties(*names)
+          if !names.empty?
+            names = names.map { |name| name.to_sym }
+
+            # Add or change properties that are not part of the identity.
+            names.each do |name|
+              property = properties[name]
+              if !property
+                self.property name, instance_variable_name: false, identity: true
+              elsif !property.identity?
+                self.property name, identity: true
+              end
+            end
+
+            # If identity_properties *excludes* something which is currently part of
+            # the identity, mark it as identity: false.
+            properties.each do |name,property|
+              if property.identity? && !names.include?(name)
+
+                self.property name, identity: false
+              end
+            end
+          end
+
+          result = properties.values.select { |property| property.identity? }
+          result = [ properties[:name] ] if result.empty?
+          result
+        end
+
+        def included(other)
+          other.extend ClassMethods
+        end
+      end
+
+      def self.included(other)
+        other.extend ClassMethods
+      end
+
+      include Chef::Mixin::ParamsValidate
+
+      #
+      # Whether this property has been set (or whether it has a default that has
+      # been retrieved).
+      #
+      # @param name [Symbol] The name of the property.
+      # @return [Boolean] `true` if the property has been set.
+      #
+      def property_is_set?(name)
+        property = self.class.properties[name.to_sym]
+        raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+        property.is_set?(self)
+      end
+
+      #
+      # Clear this property as if it had never been set. It will thereafter return
+      # the default.
+      # been retrieved).
+      #
+      # @param name [Symbol] The name of the property.
+      #
+      def reset_property(name)
+        property = self.class.properties[name.to_sym]
+        raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+        property.reset(self)
+      end
+    end
+  end
+end
diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb
index bc25af6..095e273 100644
--- a/lib/chef/mixin/provides.rb
+++ b/lib/chef/mixin/provides.rb
@@ -4,29 +4,23 @@ require 'chef/mixin/descendants_tracker'
 class Chef
   module Mixin
     module Provides
+      # TODO no longer needed, remove or deprecate?
       include Chef::Mixin::DescendantsTracker
 
-      def node_map
-        @node_map ||= Chef::NodeMap.new
+      def provides(short_name, opts={}, &block)
+        raise NotImplementedError, :provides
       end
 
-      def provides(short_name, opts={}, &block)
-        if !short_name.kind_of?(Symbol)
-          # YAGNI: this is probably completely unnecessary and can be removed?
-          Chef::Log.deprecation "Passing a non-Symbol to Chef::Resource#provides will be removed"
-          if short_name.kind_of?(String)
-            short_name.downcase!
-            short_name.gsub!(/\s/, "_")
-          end
-          short_name = short_name.to_sym
-        end
-        node_map.set(short_name, true, opts, &block)
+      # Check whether this resource provides the resource_name DSL for the given
+      # node.  TODO remove this when we stop checking unregistered things.
+      def provides?(node, resource)
+        raise NotImplementedError, :provides?
       end
 
-      # provides a node on the resource (early binding)
-      def provides?(node, resource_name)
-        resource_name = resource_name.resource_name if resource_name.is_a?(Chef::Resource)
-        node_map.get(node, resource_name)
+      # Get the list of recipe DSL this resource is responsible for on the given
+      # node.
+      def provided_as(node)
+        node_map.list(node)
       end
     end
   end
diff --git a/lib/chef/mixin/which.rb b/lib/chef/mixin/proxified_socket.rb
similarity index 53%
copy from lib/chef/mixin/which.rb
copy to lib/chef/mixin/proxified_socket.rb
index 4179c97..3fda008 100644
--- a/lib/chef/mixin/which.rb
+++ b/lib/chef/mixin/proxified_socket.rb
@@ -1,6 +1,5 @@
-#--
-# Author:: Lamont Granquist <lamont at getchef.io>
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# Author:: Tyler Ball (<tball at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,24 +13,26 @@
 # 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 'proxifier'
 
 class Chef
   module Mixin
-    module Which
-      def which(cmd, opts = {})
-        extra_path =
-          if opts[:extra_path].nil?
-            [ '/bin', '/usr/bin', '/sbin', '/usr/sbin' ]
-          else
-            [ opts[:extra_path] ].flatten
-          end
-        paths = ENV['PATH'].split(File::PATH_SEPARATOR) + extra_path
-        paths.each do |path|
-          filename = File.join(path, cmd)
-          return filename if File.executable?(filename)
+    module ProxifiedSocket
+
+      # This looks at the environment variables and leverages Proxifier to
+      # make the TCPSocket respect ENV['https_proxy'] or ENV['http_proxy'] if
+      # they are present
+      def proxified_socket(host, port)
+        proxy = ENV['https_proxy'] || ENV['http_proxy'] || false
+        if proxy
+          Proxifier.Proxy(proxy, no_proxy: ENV['no_proxy']).open(host, port)
+        else
+          TCPSocket.new(host, port)
         end
-        false
       end
+
     end
   end
 end
diff --git a/spec/support/lib/chef/resource/cat.rb b/lib/chef/mixin/subclass_directive.rb
similarity index 62%
copy from spec/support/lib/chef/resource/cat.rb
copy to lib/chef/mixin/subclass_directive.rb
index ecca50c..0f386b6 100644
--- a/spec/support/lib/chef/resource/cat.rb
+++ b/lib/chef/mixin/subclass_directive.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,22 +17,20 @@
 #
 
 class Chef
-  class Resource
-    class Cat < Chef::Resource
-
-      attr_accessor :action
+  module Mixin
+    module SubclassDirective
+      def subclass_directive(sym)
+        define_singleton_method sym do
+          instance_variable_set(:"@#{sym}", true)
+        end
 
-      def initialize(name, run_context=nil)
-        @resource_name = :cat
-        super
-        @action = "sell"
-      end
+        define_singleton_method :"#{sym}?" do
+          !!instance_variable_get(:"@#{sym}")
+        end
 
-      def pretty_kitty(arg=nil)
-        if arg == true or arg == false
-          @pretty_kitty = arg
+        define_method :"#{sym}?" do
+          self.class.send(:"#{sym}?")
         end
-        @pretty_kitty
       end
     end
   end
diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb
index d705a9e..db9a2f6 100644
--- a/lib/chef/mixin/template.rb
+++ b/lib/chef/mixin/template.rb
@@ -44,6 +44,52 @@ class Chef
 
         attr_reader :_extension_modules
 
+        #
+        # Helpers for adding context of which resource is rendering the template (CHEF-5012)
+        #
+
+        # name of the cookbook containing the template resource, e.g.:
+        #   test
+        #
+        # @return [String] cookbook name
+        attr_reader :cookbook_name
+
+        # name of the recipe containing the template resource, e.g.:
+        #   default
+        #
+        # @return [String] recipe name
+        attr_reader :recipe_name
+
+        # string representation of the line in the recipe containing the template resource, e.g.:
+        #   /Users/lamont/solo/cookbooks/test/recipes/default.rb:2:in `from_file'
+        #
+        # @return [String] recipe line
+        attr_reader :recipe_line_string
+
+        # path to the recipe containing the template resource, e.g.:
+        #   /Users/lamont/solo/cookbooks/test/recipes/default.rb
+        #
+        # @return [String] recipe path
+        attr_reader :recipe_path
+
+        # line in the recipe containing the template reosurce, e.g.:
+        #   2
+        #
+        # @return [String] recipe line
+        attr_reader :recipe_line
+
+        # name of the template source itself, e.g.:
+        #   foo.erb
+        #
+        # @return [String] template name
+        attr_reader :template_name
+
+        # path to the template source itself, e.g.:
+        #   /Users/lamont/solo/cookbooks/test/templates/default/foo.erb
+        #
+        # @return [String] template path
+        attr_reader :template_path
+
         def initialize(variables)
           super
           @_extension_modules = []
@@ -62,6 +108,7 @@ class Chef
                 "include a node variable if you plan to use it."
         end
 
+
         #
         # Takes the name of the partial, plus a hash of options. Returns a
         # string that contains the result of the evaluation of the partial.
@@ -89,6 +136,7 @@ class Chef
           raise "You cannot render partials in this context" unless @template_finder
 
           partial_variables = options.delete(:variables) || _public_instance_variables
+          partial_variables[:template_finder] = @template_finder
           partial_context = self.class.new(partial_variables)
           partial_context._extend_modules(@_extension_modules)
 
diff --git a/lib/chef/resource/pacman_package.rb b/lib/chef/mixin/unformatter.rb
similarity index 65%
copy from lib/chef/resource/pacman_package.rb
copy to lib/chef/mixin/unformatter.rb
index 4c45dd0..aa5977e 100644
--- a/lib/chef/resource/pacman_package.rb
+++ b/lib/chef/mixin/unformatter.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Jan Zimmek (<jan.zimmek at web.de>)
-# Copyright:: Copyright (c) 2010 Jan Zimmek
+# Author:: Jay Mundrawala (<jdm at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,17 +16,15 @@
 # limitations under the License.
 #
 
-require 'chef/resource/package'
-
 class Chef
-  class Resource
-    class PacmanPackage < Chef::Resource::Package
-
-      provides :pacman_package, os: "linux"
+  module Mixin
+    module Unformatter
 
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :pacman_package
+      def write(message)
+        data = message.match(/(\[.+?\] )?([\w]+):(.*)$/)
+        self.send(data[2].downcase.chomp.to_sym, data[3].strip)
+      rescue NoMethodError
+        self.send(:info, message)
       end
 
     end
diff --git a/lib/chef/util/powershell/ps_credential.rb b/lib/chef/mixin/uris.rb
similarity index 52%
copy from lib/chef/util/powershell/ps_credential.rb
copy to lib/chef/mixin/uris.rb
index 01f8c27..0136b55 100644
--- a/lib/chef/util/powershell/ps_credential.rb
+++ b/lib/chef/mixin/uris.rb
@@ -1,7 +1,7 @@
 #
 # Author:: Jay Mundrawala (<jdm at chef.io>)
-#
 # Copyright:: Copyright (c) 2015 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.
@@ -16,23 +16,29 @@
 # limitations under the License.
 #
 
-require 'chef/win32/crypto' if Chef::Platform.windows?
+require 'uri'
 
-class Chef::Util::Powershell
-  class PSCredential
-    def initialize(username, password)
-      @username = username
-      @password = password
-    end
+class Chef
+  module Mixin
+    module Uris
+      # uri_scheme? returns true if the string starts with
+      # scheme://
+      # For example, it will match http://foo.bar.com
+      def uri_scheme?(source)
+        # From open-uri
+        !!(%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ source)
+      end
 
-    def to_psobject
-      "New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
-    end
 
-    private
+      def as_uri(source)
+        begin
+          URI.parse(source)
+        rescue URI::InvalidURIError
+          Chef::Log.warn("#{source} was an invalid URI. Trying to escape invalid characters")
+          URI.parse(URI.escape(source))
+        end
+      end
 
-    def encrypt(str)
-      Chef::ReservedNames::Win32::Crypto.encrypt(str)
     end
   end
 end
diff --git a/lib/chef/mixin/which.rb b/lib/chef/mixin/which.rb
index 4179c97..4e1c516 100644
--- a/lib/chef/mixin/which.rb
+++ b/lib/chef/mixin/which.rb
@@ -28,7 +28,7 @@ class Chef
         paths = ENV['PATH'].split(File::PATH_SEPARATOR) + extra_path
         paths.each do |path|
           filename = File.join(path, cmd)
-          return filename if File.executable?(filename)
+          return filename if File.executable?(Chef.path_to(filename))
         end
         false
       end
diff --git a/lib/chef/mixin/wide_string.rb b/lib/chef/mixin/wide_string.rb
new file mode 100644
index 0000000..0c32b76
--- /dev/null
+++ b/lib/chef/mixin/wide_string.rb
@@ -0,0 +1,72 @@
+#
+# Author:: Jay Mundrawala(<jdm at chef.io>)
+# Copyright:: Copyright 2015 Chef Software
+# 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.
+#
+
+class Chef
+  module Mixin
+    module WideString
+
+      def wstring(str)
+        if str.nil? || str.encoding == Encoding::UTF_16LE
+          str
+        else
+          utf8_to_wide(str)
+        end
+      end
+
+      def utf8_to_wide(ustring)
+        # ensure it is actually UTF-8
+        # Ruby likes to mark binary data as ASCII-8BIT
+        ustring = (ustring + "").force_encoding('UTF-8') if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8"
+
+        # ensure we have the double-null termination Windows Wide likes
+        ustring = ustring + "\000\000" if ustring.length == 0 or ustring[-1].chr != "\000"
+
+        # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
+        ustring = begin
+          if ustring.respond_to?(:encode)
+            ustring.encode('UTF-16LE')
+          else
+            require 'iconv'
+            Iconv.conv("UTF-16LE", "UTF-8", ustring)
+          end
+        end
+        ustring
+      end
+
+      def wide_to_utf8(wstring)
+        # ensure it is actually UTF-16LE
+        # Ruby likes to mark binary data as ASCII-8BIT
+        wstring = wstring.force_encoding('UTF-16LE') if wstring.respond_to?(:force_encoding)
+
+        # encode it all as UTF-8
+        wstring = begin
+          if wstring.respond_to?(:encode)
+            wstring.encode('UTF-8')
+          else
+            require 'iconv'
+            Iconv.conv("UTF-8", "UTF-16LE", wstring)
+          end
+        end
+        # remove trailing CRLF and NULL characters
+        wstring.strip!
+        wstring
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb
index a0ac34f..744001f 100644
--- a/lib/chef/mixin/windows_architecture_helper.rb
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -19,19 +19,13 @@
 
 require 'chef/exceptions'
 require 'chef/platform/query_helpers'
-require 'win32/api' if Chef::Platform.windows?
-require 'chef/win32/api/process' if Chef::Platform.windows?
-require 'chef/win32/api/error' if Chef::Platform.windows?
+require 'chef/win32/process' if Chef::Platform.windows?
+require 'chef/win32/system' if Chef::Platform.windows?
 
 class Chef
   module Mixin
     module WindowsArchitectureHelper
 
-      if Chef::Platform.windows?
-        include Chef::ReservedNames::Win32::API::Process
-        include Chef::ReservedNames::Win32::API::Error
-      end
-
       def node_windows_architecture(node)
         node[:kernel][:machine].to_sym
       end
@@ -42,7 +36,17 @@ class Chef
           is_i386_process_on_x86_64_windows?
       end
 
-      def with_os_architecture(node)
+      def forced_32bit_override_required?(node, desired_architecture)
+        desired_architecture == :i386 &&
+          node_windows_architecture(node) == :x86_64 &&
+          !is_i386_process_on_x86_64_windows?
+      end
+
+      def wow64_directory
+        Chef::ReservedNames::Win32::System.get_system_wow64_directory
+      end
+
+      def with_os_architecture(node, architecture: nil)
         node ||= begin
           os_arch = ENV['PROCESSOR_ARCHITEW6432'] ||
                     ENV['PROCESSOR_ARCHITECTURE']
@@ -51,9 +55,12 @@ class Chef
             n[:kernel][:machine] = os_arch == 'AMD64' ? :x86_64 : :i386
           end
         end
+
+        architecture ||= node_windows_architecture(node)
+
         wow64_redirection_state = nil
 
-        if wow64_architecture_override_required?(node, node_windows_architecture(node))
+        if wow64_architecture_override_required?(node, architecture)
           wow64_redirection_state = disable_wow64_file_redirection(node)
         end
 
@@ -85,49 +92,21 @@ class Chef
 
       def is_i386_process_on_x86_64_windows?
         if Chef::Platform.windows?
-          is_64_bit_process_result = FFI::MemoryPointer.new(:int)
-
-          # The return value of IsWow64Process is nonzero value if the API call succeeds.
-          # The result data are returned in the last parameter, not the return value.
-          call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result)
-
-          # The result is nonzero if IsWow64Process's calling process, in the case here
-          # this process, is running under WOW64, i.e. the result is nonzero if this
-          # process is 32-bit (aka :i386).
-          result = (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0)
+          Chef::ReservedNames::Win32::Process.is_wow64_process
         else
           false
         end
       end
 
       def disable_wow64_file_redirection( node )
-        original_redirection_state = ['0'].pack('P')
-
         if ( ( node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
-          win32_wow_64_disable_wow_64_fs_redirection =
-            ::Win32::API.new('Wow64DisableWow64FsRedirection', 'P', 'L', 'kernel32')
-
-          succeeded = win32_wow_64_disable_wow_64_fs_redirection.call(original_redirection_state)
-
-          if succeeded == 0
-            raise Win32APIError "Failed to disable Wow64 file redirection"
-          end
-
+          Chef::ReservedNames::Win32::System.wow64_disable_wow64_fs_redirection
         end
-
-        original_redirection_state
       end
 
       def restore_wow64_file_redirection( node, original_redirection_state )
         if ( (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
-          win32_wow_64_revert_wow_64_fs_redirection =
-            ::Win32::API.new('Wow64RevertWow64FsRedirection', 'P', 'L', 'kernel32')
-
-          succeeded = win32_wow_64_revert_wow_64_fs_redirection.call(original_redirection_state)
-
-          if succeeded == 0
-            raise Win32APIError "Failed to revert Wow64 file redirection"
-          end
+          Chef::ReservedNames::Win32::System.wow64_revert_wow64_fs_redirection(original_redirection_state)
         end
       end
 
diff --git a/lib/chef/mixin/windows_env_helper.rb b/lib/chef/mixin/windows_env_helper.rb
index 490b235..cd12b42 100644
--- a/lib/chef/mixin/windows_env_helper.rb
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -18,13 +18,16 @@
 
 
 require 'chef/exceptions'
+require 'chef/mixin/wide_string'
 require 'chef/platform/query_helpers'
 require 'chef/win32/error' if Chef::Platform.windows?
 require 'chef/win32/api/system' if Chef::Platform.windows?
+require 'chef/win32/api/unicode' if Chef::Platform.windows?
 
 class Chef
   module Mixin
     module WindowsEnvHelper
+      include Chef::Mixin::WideString
 
       if Chef::Platform.windows?
         include Chef::ReservedNames::Win32::API::System
@@ -39,7 +42,16 @@ class Chef
 
       def broadcast_env_change
         flags = SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG
-        SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string('Environment').address, flags, 5000, nil)
+        # for why two calls, see:
+        # http://stackoverflow.com/questions/4968373/why-doesnt-sendmessagetimeout-update-the-environment-variables
+        if ( SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string('Environment').address, flags, 5000, nil) == 0 )
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+        if ( SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string(
+            utf8_to_wide('Environment')
+        ).address, flags, 5000, nil) == 0 )
+          Chef::ReservedNames::Win32::Error.raise!
+        end
       end
 
       def expand_path(path)
diff --git a/lib/chef/monkey_patches/webrick-utils.rb b/lib/chef/monkey_patches/webrick-utils.rb
new file mode 100644
index 0000000..57f6db5
--- /dev/null
+++ b/lib/chef/monkey_patches/webrick-utils.rb
@@ -0,0 +1,51 @@
+require 'webrick/utils'
+
+module WEBrick
+  module Utils
+    ##
+    # Creates TCP server sockets bound to +address+:+port+ and returns them.
+    #
+    # It will create IPV4 and IPV6 sockets on all interfaces.
+    #
+    # NOTE: We need to monkey patch this method because
+    # create_listeners on Windows with Ruby > 2.0.0 does not
+    # raise an error if we're already listening on a port.
+    #
+    def create_listeners(address, port, logger=nil)
+      #
+      # utils.rb -- Miscellaneous utilities
+      #
+      # Author: IPR -- Internet Programming with Ruby -- writers
+      # Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+      # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+      # reserved.
+      #
+      # $IPR: utils.rb,v 1.10 2003/02/16 22:22:54 gotoyuzo Exp $
+      unless port
+        raise ArgumentError, "must specify port"
+      end
+      res = Socket::getaddrinfo(address, port,
+                                Socket::AF_UNSPEC,   # address family
+                                Socket::SOCK_STREAM, # socket type
+                                0,                   # protocol
+                                Socket::AI_PASSIVE)  # flag
+      last_error = nil
+      sockets = []
+      res.each{|ai|
+        begin
+          logger.debug("TCPServer.new(#{ai[3]}, #{port})") if logger
+          sock = TCPServer.new(ai[3], port)
+          port = sock.addr[1] if port == 0
+          Utils::set_close_on_exec(sock)
+          sockets << sock
+        rescue => ex
+          logger.warn("TCPServer Error: #{ex}") if logger
+          last_error  = ex
+        end
+      }
+      raise last_error if sockets.empty?
+      return sockets
+    end
+    module_function :create_listeners
+  end
+end
diff --git a/lib/chef/monkey_patches/win32/registry.rb b/lib/chef/monkey_patches/win32/registry.rb
new file mode 100644
index 0000000..2ce2ac9
--- /dev/null
+++ b/lib/chef/monkey_patches/win32/registry.rb
@@ -0,0 +1,72 @@
+#
+# Copyright:: Copyright 2015 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/win32/api/registry'
+require 'chef/win32/unicode'
+require 'win32/registry'
+
+module Win32
+  class Registry
+
+    module API
+
+      extend Chef::ReservedNames::Win32::API::Registry
+
+      module_function
+
+      if RUBY_VERSION =~ /^2\.1/
+        # ::Win32::Registry#delete_value is broken in Ruby 2.1 (up to Ruby 2.1.6).
+        # This should be resolved in a later release (see note #9 in link below).
+        # https://bugs.ruby-lang.org/issues/10820
+        def DeleteValue(hkey, name)
+          check RegDeleteValueW(hkey, name.to_wstring)
+        end
+      end
+
+      # ::Win32::Registry#delete_key uses RegDeleteKeyW. We need to use
+      # RegDeleteKeyExW to properly support WOW64 systems.
+      def DeleteKey(hkey, name)
+        check RegDeleteKeyExW(hkey, name.to_wstring, 0, 0)
+      end
+
+    end
+
+    if RUBY_VERSION =~ /^2.1/
+      # ::Win32::Registry#write does not correctly handle data in Ruby 2.1 (up to Ruby 2.1.6).
+      # https://bugs.ruby-lang.org/issues/11439
+      def write(name, type, data)
+        case type
+        when REG_SZ, REG_EXPAND_SZ
+          data = data.to_s.encode(WCHAR) + WCHAR_NUL
+        when REG_MULTI_SZ
+          data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL << WCHAR_NUL
+        when REG_BINARY
+          data = data.to_s
+        when REG_DWORD
+          data = API.packdw(data.to_i)
+        when REG_DWORD_BIG_ENDIAN
+          data = [data.to_i].pack('N')
+        when REG_QWORD
+          data = API.packqw(data.to_i)
+        else
+          raise TypeError, "Unsupported type #{type}"
+        end
+        API.SetValue(@hkey, name, type, data, data.bytesize)
+      end
+    end
+  end
+end
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index 9823185..b04e607 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -63,6 +63,8 @@ class Chef
 
     include Chef::Mixin::ParamsValidate
 
+    NULL_ARG = Object.new
+
     # Create a new Chef::Node object.
     def initialize(chef_server_rest: nil)
       @chef_server_rest = chef_server_rest
@@ -72,18 +74,38 @@ class Chef
       @primary_runlist = Chef::RunList.new
       @override_runlist = Chef::RunList.new
 
+      @policy_name = nil
+      @policy_group = nil
+
       @attributes = Chef::Node::Attribute.new({}, {}, {}, {})
 
       @run_state = {}
     end
 
+    # after the run_context has been set on the node, go through the cookbook_collection
+    # and setup the node[:cookbooks] attribute so that it is published in the node object
+    def set_cookbook_attribute
+      return unless run_context.cookbook_collection
+      run_context.cookbook_collection.each do |cookbook_name, cookbook|
+        automatic_attrs[:cookbooks][cookbook_name][:version] = cookbook.version
+      end
+    end
+
     # Used by DSL
     def node
       self
     end
 
     def chef_server_rest
-      @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url])
+      # for saving node data we use validate_utf8: false which will not
+      # raise an exception on bad utf8 data, but will replace the bad
+      # characters and render valid JSON.
+      @chef_server_rest ||= Chef::REST.new(
+        Chef::Config[:chef_server_url],
+        Chef::Config[:node_name],
+        Chef::Config[:client_key],
+        validate_utf8: false,
+      )
     end
 
     # Set the name of this Node, or return the current name.
@@ -115,6 +137,50 @@ class Chef
 
     alias :environment :chef_environment
 
+    # The `policy_name` for this node. Setting this to a non-nil value will
+    # enable policyfile mode when `chef-client` is run. If set in the config
+    # file or in node json, running `chef-client` will update this value.
+    #
+    # @see Chef::PolicyBuilder::Dynamic
+    # @see Chef::PolicyBuilder::Policyfile
+    #
+    # @param arg [String] the new policy_name value
+    # @return [String] the current policy_name, or the one you just set
+    def policy_name(arg=NULL_ARG)
+      return @policy_name if arg.equal?(NULL_ARG)
+      validate({policy_name: arg}, { policy_name: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } })
+      @policy_name = arg
+    end
+
+    # A "non-DSL-style" setter for `policy_name`
+    #
+    # @see #policy_name
+    def policy_name=(policy_name)
+      policy_name(policy_name)
+    end
+
+    # The `policy_group` for this node. Setting this to a non-nil value will
+    # enable policyfile mode when `chef-client` is run. If set in the config
+    # file or in node json, running `chef-client` will update this value.
+    #
+    # @see Chef::PolicyBuilder::Dynamic
+    # @see Chef::PolicyBuilder::Policyfile
+    #
+    # @param arg [String] the new policy_group value
+    # @return [String] the current policy_group, or the one you just set
+    def policy_group(arg=NULL_ARG)
+      return @policy_group if arg.equal?(NULL_ARG)
+      validate({policy_group: arg}, { policy_group: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } })
+      @policy_group = arg
+    end
+
+    # A "non-DSL-style" setter for `policy_group`
+    #
+    # @see #policy_group
+    def policy_group=(policy_group)
+      policy_group(policy_group)
+    end
+
     def attributes
       @attributes
     end
@@ -244,6 +310,7 @@ class Chef
     # saved back to the node and be searchable
     def loaded_recipe(cookbook, recipe)
       fully_qualified_recipe = "#{cookbook}::#{recipe}"
+
       automatic_attrs[:recipes] << fully_qualified_recipe unless Array(self[:recipes]).include?(fully_qualified_recipe)
     end
 
@@ -297,6 +364,7 @@ class Chef
     # Consumes the combined run_list and other attributes in +attrs+
     def consume_attributes(attrs)
       normal_attrs_to_merge = consume_run_list(attrs)
+      normal_attrs_to_merge = consume_chef_environment(normal_attrs_to_merge)
       Chef::Log.debug("Applying attributes from json file")
       self.normal_attrs = Chef::Mixin::DeepMerge.merge(normal_attrs,normal_attrs_to_merge)
       self.tags # make sure they're defined
@@ -308,12 +376,12 @@ class Chef
       normal[:tags]
     end
 
-    def tag(*tags)
-      tags.each do |tag|
-        self.normal[:tags].push(tag.to_s) unless self[:tags].include? tag.to_s
+    def tag(*args)
+      args.each do |tag|
+        tags.push(tag.to_s) unless tags.include? tag.to_s
       end
 
-      self[:tags]
+      tags
     end
 
     # Extracts the run list from +attrs+ and applies it. Returns the remaining attributes
@@ -329,6 +397,24 @@ class Chef
       attrs
     end
 
+    # chef_environment when set in -j JSON will take precedence over
+    # -E ENVIRONMENT. Ideally, IMO, the order of precedence should be (lowest to
+    #  highest):
+    #   config_file
+    #   -j JSON
+    #   -E ENVIRONMENT
+    # so that users could reuse their JSON and override the chef_environment
+    # configured within it with -E ENVIRONMENT. Because command line options are
+    # merged with Chef::Config there is currently no way to distinguish between
+    # an environment set via config from an environment set via command line.
+    def consume_chef_environment(attrs)
+      attrs = attrs ? attrs.dup : {}
+      if env = attrs.delete("chef_environment")
+        chef_environment(env)
+      end
+      attrs
+    end
+
     # Clear defaults and overrides, so that any deleted attributes
     # between runs are still gone.
     def reset_defaults_and_overrides
@@ -354,7 +440,8 @@ class Chef
 
       self.tags # make sure they're defined
 
-      automatic_attrs[:recipes] = expansion.recipes
+      automatic_attrs[:recipes] = expansion.recipes.with_duplicate_names
+      automatic_attrs[:expanded_run_list] = expansion.recipes.with_fully_qualified_names_and_version_constraints
       automatic_attrs[:roles] = expansion.roles
 
       apply_expansion_attributes(expansion)
@@ -423,6 +510,14 @@ class Chef
         #Render correctly for run_list items so malformed json does not result
         "run_list" => @primary_runlist.run_list.map { |item| item.to_s }
       }
+      # Chef Server rejects node JSON with extra keys; prior to 12.3,
+      # "policy_name" and "policy_group" are unknown; after 12.3 they are
+      # optional, therefore only including them in the JSON if present
+      # maximizes compatibility for most people.
+      unless policy_group.nil? && policy_name.nil?
+        result["policy_name"] = policy_name
+        result["policy_group"] = policy_group
+      end
       result
     end
 
@@ -454,6 +549,10 @@ class Chef
       else
         o["recipes"].each { |r| node.recipes << r }
       end
+
+      node.policy_name = o["policy_name"] if o.has_key?("policy_name")
+      node.policy_group = o["policy_group"] if o.has_key?("policy_group")
+
       node
     end
 
@@ -510,13 +609,21 @@ class Chef
       # so then POST to create.
       begin
         if Chef::Config[:why_run]
-          Chef::Log.warn("In whyrun mode, so NOT performing node save.")
+          Chef::Log.warn("In why-run mode, so NOT performing node save.")
         else
           chef_server_rest.put_rest("nodes/#{name}", data_for_save)
         end
       rescue Net::HTTPServerException => e
-        raise e unless e.response.code == "404"
-        chef_server_rest.post_rest("nodes", data_for_save)
+        if e.response.code == "404"
+          chef_server_rest.post_rest("nodes", data_for_save)
+        # Chef Server before 12.3 rejects node JSON with 'policy_name' or
+        # 'policy_group' keys, but 'policy_name' will be detected first.
+        # Backcompat can be removed in 13.0
+        elsif e.response.code == "400" && e.response.body.include?("Invalid key policy_name")
+          save_without_policyfile_attrs
+        else
+          raise
+        end
       end
       self
     end
@@ -525,18 +632,51 @@ class Chef
     def create
       chef_server_rest.post_rest("nodes", data_for_save)
       self
+    rescue Net::HTTPServerException => e
+      # Chef Server before 12.3 rejects node JSON with 'policy_name' or
+      # 'policy_group' keys, but 'policy_name' will be detected first.
+      # Backcompat can be removed in 13.0
+      if e.response.code == "400" && e.response.body.include?("Invalid key policy_name")
+        chef_server_rest.post_rest("nodes", data_for_save_without_policyfile_attrs)
+      else
+        raise
+      end
     end
 
     def to_s
       "node[#{name}]"
     end
 
+    def ==(other)
+      if other.kind_of?(self.class)
+         self.name == other.name
+      else
+        false
+      end
+    end
+
     def <=>(other_node)
       self.name <=> other_node.name
     end
 
     private
 
+    def save_without_policyfile_attrs
+      trimmed_data = data_for_save_without_policyfile_attrs
+
+      chef_server_rest.put_rest("nodes/#{name}", trimmed_data)
+    rescue Net::HTTPServerException => e
+      raise e unless e.response.code == "404"
+      chef_server_rest.post_rest("nodes", trimmed_data)
+    end
+
+    def data_for_save_without_policyfile_attrs
+      data_for_save.tap do |trimmed_data|
+        trimmed_data.delete("policy_name")
+        trimmed_data.delete("policy_group")
+      end
+    end
+
     def data_for_save
       data = for_json
       ["automatic", "default", "normal", "override"].each do |level|
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index 2ca6d9b..751f957 100644
--- a/lib/chef/node_map.rb
+++ b/lib/chef/node_map.rb
@@ -19,128 +19,204 @@
 class Chef
   class NodeMap
 
-    VALID_OPTS = [
-      :on_platform,
-      :on_platforms,
-      :platform,
-      :os,
-      :platform_family,
-    ]
-
-    DEPRECATED_OPTS = [
-      :on_platform,
-      :on_platforms,
-    ]
-
-    # Create a new NodeMap
     #
-    def initialize
-      @map = {}
-    end
-
     # Set a key/value pair on the map with a filter.  The filter must be true
     # when applied to the node in order to retrieve the value.
     #
     # @param key [Object] Key to store
     # @param value [Object] Value associated with the key
     # @param filters [Hash] Node filter options to apply to key retrieval
+    #
     # @yield [node] Arbitrary node filter as a block which takes a node argument
+    #
     # @return [NodeMap] Returns self for possible chaining
     #
-    def set(key, value, filters = {}, &block)
-      validate_filter!(filters)
-      deprecate_filter!(filters)
-      @map[key] ||= []
-      # we match on the first value we find, so we want to unshift so that the
-      # last setter wins
-      # FIXME: need a test for this behavior
-      @map[key].unshift({ filters: filters, block: block, value: value })
-      self
+    def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: nil, &block)
+      Chef.log_deprecation("The on_platform option to node_map has been deprecated") if on_platform
+      Chef.log_deprecation("The on_platforms option to node_map has been deprecated") if on_platforms
+      platform ||= on_platform || on_platforms
+      filters = {}
+      filters[:platform] = platform if platform
+      filters[:platform_version] = platform_version if platform_version
+      filters[:platform_family] = platform_family if platform_family
+      filters[:os] = os if os
+      new_matcher = { value: value, filters: filters }
+      new_matcher[:block] = block if block
+      new_matcher[:canonical] = canonical if canonical
+      new_matcher[:override] = override if override
+
+      # The map is sorted in order of preference already; we just need to find
+      # our place in it (just before the first value with the same preference level).
+      insert_at = nil
+      map[key] ||= []
+      map[key].each_with_index do |matcher,index|
+        cmp = compare_matchers(key, new_matcher, matcher)
+        insert_at ||= index if cmp && cmp <= 0
+      end
+      if insert_at
+        map[key].insert(insert_at, new_matcher)
+      else
+        map[key] << new_matcher
+      end
+      map
     end
 
+    #
     # Get a value from the NodeMap via applying the node to the filters that
     # were set on the key.
     #
-    # @param node [Chef::Node] The Chef::Node object for the run
+    # @param node [Chef::Node] The Chef::Node object for the run, or `nil` to
+    #   ignore all filters.
+    # @param key [Object] Key to look up
+    # @param canonical [Boolean] `true` or `false` to match canonical or
+    #   non-canonical values only. `nil` to ignore canonicality.  Default: `nil`
+    #
+    # @return [Object] Value
+    #
+    def get(node, key, canonical: nil)
+      raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil?
+      list(node, key, canonical: canonical).first
+    end
+
+    #
+    # List all matches for the given node and key from the NodeMap, from
+    # most-recently added to oldest.
+    #
+    # @param node [Chef::Node] The Chef::Node object for the run, or `nil` to
+    #   ignore all filters.
     # @param key [Object] Key to look up
+    # @param canonical [Boolean] `true` or `false` to match canonical or
+    #   non-canonical values only. `nil` to ignore canonicality.  Default: `nil`
+    #
     # @return [Object] Value
     #
-    def get(node, key)
-      # FIXME: real exception
-      raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node)
-      return nil unless @map.has_key?(key)
-      @map[key].each do |matcher|
-        if filters_match?(node, matcher[:filters]) &&
-          block_matches?(node, matcher[:block])
-          return matcher[:value]
+    def list(node, key, canonical: nil)
+      raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil?
+      return [] unless map.has_key?(key)
+      map[key].select do |matcher|
+        node_matches?(node, matcher) && canonical_matches?(canonical, matcher)
+      end.map { |matcher| matcher[:value] }
+    end
+
+    # Seriously, don't use this, it's nearly certain to change on you
+    # @return remaining
+    # @api private
+    def delete_canonical(key, value)
+      remaining = map[key]
+      if remaining
+        remaining.delete_if { |matcher| matcher[:canonical] && Array(matcher[:value]) == Array(value) }
+        if remaining.empty?
+          map.delete(key)
+          remaining = nil
         end
       end
-      nil
+      remaining
     end
 
-    private
+    protected
 
-    # only allow valid filter options
-    def validate_filter!(filters)
-      filters.each_key do |key|
-        # FIXME: real exception
-        raise "Bad key #{key} in Chef::NodeMap filter expression" unless VALID_OPTS.include?(key)
-      end
+    #
+    # Succeeds if:
+    # - no negative matches (!value)
+    # - at least one positive match (value or :all), or no positive filters
+    #
+    def matches_black_white_list?(node, filters, attribute)
+      # It's super common for the filter to be nil.  Catch that so we don't
+      # spend any time here.
+      return true if !filters[attribute]
+      filter_values = Array(filters[attribute])
+      value = node[attribute]
+
+      # Split the blacklist and whitelist
+      blacklist, whitelist = filter_values.partition { |v| v.is_a?(String) && v.start_with?('!') }
+
+      # If any blacklist value matches, we don't match
+      return false if blacklist.any? { |v| v[1..-1] == value }
+
+      # If the whitelist is empty, or anything matches, we match.
+      whitelist.empty? || whitelist.any? { |v| v == :all || v == value }
     end
 
-    # warn on deprecated filter options
-    def deprecate_filter!(filters)
-      filters.each_key do |key|
-        Chef::Log.warn "The #{key} option to node_map has been deprecated" if DEPRECATED_OPTS.include?(key)
+    def matches_version_list?(node, filters, attribute)
+      # It's super common for the filter to be nil.  Catch that so we don't
+      # spend any time here.
+      return true if !filters[attribute]
+      filter_values = Array(filters[attribute])
+      value = node[attribute]
+
+      filter_values.empty? ||
+      Array(filter_values).any? do |v|
+        Chef::VersionConstraint::Platform.new(v).include?(value)
       end
     end
 
-    # @todo: this works fine, but is probably hard to understand
-    def negative_match(filter, param)
-      # We support strings prefaced by '!' to mean 'not'.  In particular, this is most useful
-      # for os matching on '!windows'.
-      negative_matches = filter.select { |f| f[0] == '!' }
-      return true if !negative_matches.empty? && negative_matches.include?('!' + param)
-
-      # We support the symbol :all to match everything, for backcompat, but this can and should
-      # simply be ommitted.
-      positive_matches = filter.reject { |f| f[0] == '!' || f == :all }
-      return true if !positive_matches.empty? && !positive_matches.include?(param)
+    def filters_match?(node, filters)
+      matches_black_white_list?(node, filters, :os) &&
+      matches_black_white_list?(node, filters, :platform_family) &&
+      matches_black_white_list?(node, filters, :platform) &&
+      matches_version_list?(node, filters, :platform_version)
+    end
 
-      # sorry double-negative: this means we pass this filter.
-      false
+    def block_matches?(node, block)
+      return true if block.nil?
+      block.call node
     end
 
-    def filters_match?(node, filters)
-      return true if filters.empty?
+    def node_matches?(node, matcher)
+      return true if !node
+      filters_match?(node, matcher[:filters]) && block_matches?(node, matcher[:block])
+    end
 
-      # each filter is applied in turn.  if any fail, then it shortcuts and returns false.
-      # if it passes or does not exist it succeeds and continues on.  so multiple filters are
-      # effectively joined by 'and'.  all filters can be single strings, or arrays which are
-      # effectively joined by 'or'.
+    def canonical_matches?(canonical, matcher)
+      return true if canonical.nil?
+      !!canonical == !!matcher[:canonical]
+    end
 
-      os_filter = [ filters[:os] ].flatten.compact
-      unless os_filter.empty?
-        return false if negative_match(os_filter, node[:os])
-      end
+    def compare_matchers(key, new_matcher, matcher)
+      cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:block] }
+      return cmp if cmp != 0
+      cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] }
+      return cmp if cmp != 0
+      cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform] }
+      return cmp if cmp != 0
+      cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_family] }
+      return cmp if cmp != 0
+      cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:os] }
+      return cmp if cmp != 0
+      cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:override] }
+      return cmp if cmp != 0
+      # If all things are identical, return 0
+      0
+    end
 
-      platform_family_filter = [ filters[:platform_family] ].flatten.compact
-      unless platform_family_filter.empty?
-        return false if negative_match(platform_family_filter, node[:platform_family])
+    def compare_matcher_properties(new_matcher, matcher)
+      a = yield(new_matcher)
+      b = yield(matcher)
+
+      # Check for blcacklists ('!windows'). Those always come *after* positive
+      # whitelists.
+      a_negated = Array(a).any? { |f| f.is_a?(String) && f.start_with?('!') }
+      b_negated = Array(b).any? { |f| f.is_a?(String) && f.start_with?('!') }
+      if a_negated != b_negated
+        return 1 if a_negated
+        return -1 if b_negated
       end
 
-      # :on_platform and :on_platforms here are synonyms which are deprecated
-      platform_filter = [ filters[:platform] || filters[:on_platform] || filters[:on_platforms] ].flatten.compact
-      unless platform_filter.empty?
-        return false if negative_match(platform_filter, node[:platform])
+      # We treat false / true and nil / not-nil with the same comparison
+      a = nil if a == false
+      b = nil if b == false
+      cmp = a <=> b
+      # This is the case where one is non-nil, and one is nil. The one that is
+      # nil is "greater" (i.e. it should come last).
+      if cmp.nil?
+        return 1 if a.nil?
+        return -1 if b.nil?
       end
-
-      return true
+      cmp
     end
 
-    def block_matches?(node, block)
-      return true if block.nil?
-      block.call node
+    def map
+      @map ||= {}
     end
   end
 end
diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/platform/handler_map.rb
similarity index 50%
copy from lib/chef/deprecation/warnings.rb
copy to lib/chef/platform/handler_map.rb
index 34f468f..a9551a3 100644
--- a/lib/chef/deprecation/warnings.rb
+++ b/lib/chef/platform/handler_map.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Serdar Sutay (<serdar at opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# Author:: John Keiser (<jkeiser at chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,24 +16,25 @@
 # limitations under the License.
 #
 
-class Chef
-  module Deprecation
-    module Warnings
+require 'chef/node_map'
 
-      def add_deprecation_warnings_for(method_names)
-        method_names.each do |name|
-          m = instance_method(name)
-          define_method(name) do |*args|
-            message = []
-            message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 12."
-            message << "Please update your cookbooks accordingly. Accessed from:"
-            caller[0..3].each {|l| message << l}
-            Chef::Log.deprecation message
-            super(*args)
+class Chef
+  class Platform
+    class HandlerMap < Chef::NodeMap
+      #
+      # "provides" lines with identical filters sort by class name (ascending).
+      #
+      def compare_matchers(key, new_matcher, matcher)
+        cmp = super
+        if cmp == 0
+          # Sort by class name (ascending) as well, if all other properties
+          # are exactly equal
+          if new_matcher[:value].is_a?(Class) && !new_matcher[:override]
+            cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name }
           end
         end
+        cmp
       end
-
     end
   end
 end
diff --git a/spec/support/lib/chef/resource/one_two_three_four.rb b/lib/chef/platform/priority_map.rb
similarity index 52%
copy from spec/support/lib/chef/resource/one_two_three_four.rb
copy to lib/chef/platform/priority_map.rb
index 296d2cd..0b050de 100644
--- a/spec/support/lib/chef/resource/one_two_three_four.rb
+++ b/lib/chef/platform/priority_map.rb
@@ -1,6 +1,6 @@
 #
-# Author:: John Hampton (<john at cleanoffer.com>)
-# Copyright:: Copyright (c) 2009 CleanOffer, Inc.
+# Author:: John Keiser (<jkeiser at chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,25 +16,25 @@
 # limitations under the License.
 #
 
-class Chef
-  class Resource
-    class OneTwoThreeFour < Chef::Resource
-      attr_reader :i_can_count
+require 'chef/node_map'
 
-      def initialize(name, run_context)
-        @resource_name = :one_two_three_four
-        super
+class Chef
+  class Platform
+    class PriorityMap < Chef::NodeMap
+      def priority(resource_name, priority_array, *filter)
+        set_priority_array(resource_name.to_sym, priority_array, *filter)
       end
 
-      def i_can_count(tf)
-        @i_can_count = tf
+      # @api private
+      def get_priority_array(node, key)
+        get(node, key)
       end
 
-      def something(arg=nil)
-        if arg == true or arg == false
-          @something = arg
-        end
-        @something
+      # @api private
+      def set_priority_array(key, priority_array, *filter, &block)
+        priority_array = Array(priority_array)
+        set(key, priority_array, *filter, &block)
+        priority_array
       end
     end
   end
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/platform/provider_handler_map.rb
similarity index 69%
copy from lib/chef/resource/whyrun_safe_ruby_block.rb
copy to lib/chef/platform/provider_handler_map.rb
index 6fa5383..4549d79 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/platform/provider_handler_map.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Phil Dibowitz (<phild at fb.com>)
-# Copyright:: Copyright (c) 2013 Facebook
+# Author:: John Keiser (<jkeiser at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,14 @@
 # limitations under the License.
 #
 
-class Chef
-  class Resource
-    class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :whyrun_safe_ruby_block
-      end
+require 'singleton'
+require 'chef/platform/handler_map'
 
+class Chef
+  class Platform
+    # @api private
+    class ProviderHandlerMap < Chef::Platform::HandlerMap
+      include Singleton
     end
   end
 end
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index 0d72857..9b511f0 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -20,13 +20,8 @@ require 'chef/log'
 require 'chef/exceptions'
 require 'chef/mixin/params_validate'
 require 'chef/version_constraint/platform'
-
-# This file depends on nearly every provider in chef, but requiring them
-# directly causes circular requires resulting in uninitialized constant errors.
-# Therefore, we do the includes inline rather than up top.
 require 'chef/provider'
 
-
 class Chef
   class Platform
 
@@ -34,267 +29,7 @@ class Chef
       attr_writer :platforms
 
       def platforms
-        @platforms ||= begin
-          require 'chef/providers'
-
-          {
-            :freebsd => {
-              :default => {
-                :group   => Chef::Provider::Group::Pw,
-                :user    => Chef::Provider::User::Pw,
-              }
-            },
-            :ubuntu   => {
-              :default => {
-                :package => Chef::Provider::Package::Apt,
-                :service => Chef::Provider::Service::Debian,
-              },
-              ">= 11.10" => {
-                :ifconfig => Chef::Provider::Ifconfig::Debian
-              }
-              # Chef::Provider::Service::Upstart is a candidate to be used in
-              # ubuntu versions >= 13.10 but it currently requires all the
-              # services to have an entry under /etc/init. We need to update it
-              # to use the service ctl apis in order to migrate to using it on
-              # ubuntu >= 13.10.
-            },
-            :gcel   => {
-              :default => {
-                :package => Chef::Provider::Package::Apt,
-                :service => Chef::Provider::Service::Debian,
-              }
-            },
-            :linaro   => {
-              :default => {
-                :package => Chef::Provider::Package::Apt,
-                :service => Chef::Provider::Service::Debian,
-              }
-            },
-            :raspbian   => {
-              :default => {
-                :package => Chef::Provider::Package::Apt,
-                :service => Chef::Provider::Service::Debian,
-              }
-            },
-            :linuxmint   => {
-              :default => {
-                :package => Chef::Provider::Package::Apt,
-                :service => Chef::Provider::Service::Upstart,
-              }
-            },
-            :debian => {
-              :default => {
-                :package => Chef::Provider::Package::Apt,
-                :service => Chef::Provider::Service::Debian,
-              },
-              ">= 6.0" => {
-                :service => Chef::Provider::Service::Insserv
-              },
-              ">= 7.0" => {
-                :ifconfig => Chef::Provider::Ifconfig::Debian
-              }
-            },
-            :xenserver   => {
-              :default => {
-                :service => Chef::Provider::Service::Redhat,
-                :package => Chef::Provider::Package::Yum,
-              }
-            },
-            :xcp   => {
-              :default => {
-                :service => Chef::Provider::Service::Redhat,
-                :package => Chef::Provider::Package::Yum,
-              }
-            },
-            :centos   => {
-              :default => {
-                :service => Chef::Provider::Service::Systemd,
-                :package => Chef::Provider::Package::Yum,
-                :ifconfig => Chef::Provider::Ifconfig::Redhat
-              },
-              "< 7" => {
-                :service => Chef::Provider::Service::Redhat
-              }
-            },
-            :amazon   => {
-              :default => {
-                :service => Chef::Provider::Service::Redhat,
-                :package => Chef::Provider::Package::Yum,
-              }
-            },
-            :scientific => {
-              :default => {
-                :service => Chef::Provider::Service::Systemd,
-                :package => Chef::Provider::Package::Yum,
-              },
-              "< 7" => {
-                :service => Chef::Provider::Service::Redhat
-              }
-            },
-            :fedora   => {
-              :default => {
-                :service => Chef::Provider::Service::Systemd,
-                :package => Chef::Provider::Package::Yum,
-                :ifconfig => Chef::Provider::Ifconfig::Redhat
-              },
-              "< 15" => {
-                :service => Chef::Provider::Service::Redhat
-              }
-            },
-            :opensuse     => {
-              :default => {
-                :service => Chef::Provider::Service::Redhat,
-                :package => Chef::Provider::Package::Zypper,
-                :group => Chef::Provider::Group::Suse
-              },
-              # Only OpenSuSE 12.3+ should use the Usermod group provider:
-              ">= 12.3" => {
-                :group => Chef::Provider::Group::Usermod
-              }
-            },
-            :suse     => {
-              :default => {
-                :service => Chef::Provider::Service::Systemd,
-                :package => Chef::Provider::Package::Zypper,
-                :group => Chef::Provider::Group::Gpasswd
-              },
-              "< 12.0" => {
-                :group => Chef::Provider::Group::Suse,
-                :service => Chef::Provider::Service::Redhat
-              }
-            },
-            :oracle  => {
-              :default => {
-                :service => Chef::Provider::Service::Systemd,
-                :package => Chef::Provider::Package::Yum,
-              },
-              "< 7" => {
-                :service => Chef::Provider::Service::Redhat
-              }
-            },
-            :redhat   => {
-              :default => {
-                :service => Chef::Provider::Service::Systemd,
-                :package => Chef::Provider::Package::Yum,
-                :ifconfig => Chef::Provider::Ifconfig::Redhat
-              },
-              "< 7" => {
-                :service => Chef::Provider::Service::Redhat
-              }
-            },
-            :ibm_powerkvm   => {
-              :default => {
-                :service => Chef::Provider::Service::Redhat,
-                :package => Chef::Provider::Package::Yum,
-                :ifconfig => Chef::Provider::Ifconfig::Redhat
-              }
-            },
-            :cloudlinux   => {
-              :default => {
-                :service => Chef::Provider::Service::Redhat,
-                :package => Chef::Provider::Package::Yum,
-                :ifconfig => Chef::Provider::Ifconfig::Redhat
-              }
-            },
-            :parallels   => {
-                :default => {
-                    :service => Chef::Provider::Service::Redhat,
-                    :package => Chef::Provider::Package::Yum,
-                    :ifconfig => Chef::Provider::Ifconfig::Redhat
-                }
-            },
-            :gentoo   => {
-              :default => {
-                :package => Chef::Provider::Package::Portage,
-                :service => Chef::Provider::Service::Gentoo,
-              }
-            },
-            :arch   => {
-              :default => {
-                :package => Chef::Provider::Package::Pacman,
-                :service => Chef::Provider::Service::Systemd,
-              }
-            },
-            :solaris  => {},
-            :openindiana => {
-              :default => {
-                :mount => Chef::Provider::Mount::Solaris,
-                :package => Chef::Provider::Package::Ips,
-                :group => Chef::Provider::Group::Usermod
-              }
-            },
-            :opensolaris => {
-              :default => {
-                :mount => Chef::Provider::Mount::Solaris,
-                :package => Chef::Provider::Package::Ips,
-                :group => Chef::Provider::Group::Usermod
-              }
-            },
-            :nexentacore => {
-              :default => {
-                :mount => Chef::Provider::Mount::Solaris,
-                :package => Chef::Provider::Package::Solaris,
-                :group => Chef::Provider::Group::Usermod
-              }
-            },
-            :omnios => {
-              :default => {
-                :mount => Chef::Provider::Mount::Solaris,
-                :package => Chef::Provider::Package::Ips,
-                :group => Chef::Provider::Group::Usermod,
-                :user => Chef::Provider::User::Solaris,
-              }
-            },
-            :solaris2 => {
-              :default => {
-                :mount => Chef::Provider::Mount::Solaris,
-                :package => Chef::Provider::Package::Ips,
-                :group => Chef::Provider::Group::Usermod,
-                :user => Chef::Provider::User::Solaris,
-              },
-              "< 5.11" => {
-                :mount => Chef::Provider::Mount::Solaris,
-                :package => Chef::Provider::Package::Solaris,
-                :group => Chef::Provider::Group::Usermod,
-                :user => Chef::Provider::User::Solaris,
-              }
-            },
-            :smartos => {
-              :default => {
-                :mount => Chef::Provider::Mount::Solaris,
-                :package => Chef::Provider::Package::SmartOS,
-                :group => Chef::Provider::Group::Usermod
-              }
-            },
-            :hpux => {
-              :default => {
-                :group => Chef::Provider::Group::Usermod
-              }
-            },
-            :aix => {
-              :default => {
-                :group => Chef::Provider::Group::Aix,
-                :mount => Chef::Provider::Mount::Aix,
-                :ifconfig => Chef::Provider::Ifconfig::Aix,
-                :package => Chef::Provider::Package::Aix,
-                :user => Chef::Provider::User::Aix,
-                :service => Chef::Provider::Service::Aix
-              }
-            },
-            :exherbo => {
-              :default => {
-                :package => Chef::Provider::Package::Paludis,
-                :service => Chef::Provider::Service::Systemd,
-              }
-            },
-            :default => {
-              :mount => Chef::Provider::Mount::Mount,
-              :user => Chef::Provider::User::Useradd,
-              :group => Chef::Provider::Group::Gpasswd,
-              :ifconfig => Chef::Provider::Ifconfig,
-            }
-          }
-        end
+        @platforms ||= { default: {} }
       end
 
       include Chef::Mixin::ParamsValidate
@@ -304,7 +39,7 @@ class Chef
 
         name_sym = name
         if name.kind_of?(String)
-          name.downcase!
+          name = name.downcase
           name.gsub!(/\s/, "_")
           name_sym = name.to_sym
         end
@@ -325,8 +60,6 @@ class Chef
               Chef::Log.debug("Chef::Version::Comparable does not know how to parse the platform version: #{version}")
             end
           end
-        else
-          Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)")
         end
         provider_map
       end
@@ -443,7 +176,7 @@ class Chef
                          platform_provider(platform, version, resource_type) ||
                          resource_matching_provider(platform, version, resource_type)
 
-        raise ArgumentError, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
+        raise Chef::Exceptions::ProviderNotFound, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
 
         provider_klass
       end
@@ -460,16 +193,20 @@ class Chef
           pmap.has_key?(rtkey) ? pmap[rtkey] : nil
         end
 
+        include Chef::Mixin::ConvertToClassName
+
         def resource_matching_provider(platform, version, resource_type)
           if resource_type.kind_of?(Chef::Resource)
-            begin
-              Chef::Provider.const_get(resource_type.class.to_s.split('::').last)
-            rescue NameError
-              nil
+            class_name = resource_type.class.name ? resource_type.class.name.split('::').last :
+              convert_to_class_name(resource_type.resource_name.to_s)
+
+            if Chef::Provider.const_defined?(class_name)
+              Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'provides #{convert_to_snake_case(class_name).to_sym.inspect}'.")
+              Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to use the resource's DSL.")
+              return Chef::Provider.const_get(class_name)
             end
-          else
-            nil
           end
+          nil
         end
 
     end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 1539f61..5599c74 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,92 +1,11 @@
+require 'singleton'
+require 'chef/platform/priority_map'
 
 class Chef
   class Platform
-    class ProviderPriorityMap
+    # @api private
+    class ProviderPriorityMap < Chef::Platform::PriorityMap
       include Singleton
-
-      def initialize
-        load_default_map
-      end
-
-      def get_priority_array(node, resource_name)
-        priority_map.get(node, resource_name.to_sym)
-      end
-
-      def set_priority_array(resource_name, priority_array, *filter)
-        priority(resource_name.to_sym, priority_array.to_a, *filter)
-      end
-
-      def priority(*args)
-        priority_map.set(*args)
-      end
-
-      private
-
-      def load_default_map
-        require 'chef/providers'
-
-        #
-        # Linux
-        #
-
-        # default block for linux O/Sen must come before platform_family exceptions
-        priority :service, [
-          Chef::Provider::Service::Systemd,
-          Chef::Provider::Service::Insserv,
-          Chef::Provider::Service::Redhat,
-        ], os: "linux"
-
-        priority :service, [
-          Chef::Provider::Service::Systemd,
-          Chef::Provider::Service::Arch,
-        ], platform_family: "arch"
-
-        priority :service, [
-          Chef::Provider::Service::Systemd,
-          Chef::Provider::Service::Gentoo,
-        ], platform_family: "gentoo"
-
-        priority :service, [
-          # we can determine what systemd supports accurately
-          Chef::Provider::Service::Systemd,
-          # on debian-ish system if an upstart script exists that must win over sysv types
-          Chef::Provider::Service::Upstart,
-          Chef::Provider::Service::Insserv,
-          Chef::Provider::Service::Debian,
-          Chef::Provider::Service::Invokercd,
-        ], platform_family: "debian"
-
-        priority :service, [
-          Chef::Provider::Service::Systemd,
-          Chef::Provider::Service::Insserv,
-          Chef::Provider::Service::Redhat,
-        ], platform_family: [ "rhel", "fedora", "suse" ]
-
-        #
-        # BSDen
-        #
-
-        priority :service, Chef::Provider::Service::Freebsd, os: [ "freebsd", "netbsd" ]
-        priority :service, Chef::Provider::Service::Openbsd, os: [ "openbsd" ]
-
-        #
-        # Solaris-en
-        #
-
-        priority :service, Chef::Provider::Service::Solaris, os: "solaris2"
-
-        #
-        # Mac
-        #
-
-        priority :service, Chef::Provider::Service::Macosx, os: "darwin"
-        priority :package, Chef::Provider::Package::Homebrew, os: "darwin"
-      end
-
-      def priority_map
-        require 'chef/node_map'
-        @priority_map ||= Chef::NodeMap.new
-      end
     end
   end
 end
diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb
index f7c85fb..699e987 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -21,21 +21,14 @@ class Chef
 
     class << self
       def windows?
-        if RUBY_PLATFORM =~ /mswin|mingw|windows/
-          true
-        else
-          false
-        end
+        ChefConfig.windows?
       end
 
       def windows_server_2003?
+        # WMI startup shouldn't be performed unless we're on Windows.
         return false unless windows?
         require 'wmi-lite/wmi'
 
-        # CHEF-4888: Work around ruby #2618, expected to be fixed in Ruby 2.1.0
-        # https://github.com/ruby/ruby/commit/588504b20f5cc880ad51827b93e571e32446e5db
-        # https://github.com/ruby/ruby/commit/27ed294c7134c0de582007af3c915a635a6506cd
-
         wmi = WmiLite::Wmi.new
         host = wmi.first_of('Win32_OperatingSystem')
         is_server_2003 = (host['version'] && host['version'].start_with?("5.2"))
@@ -43,17 +36,78 @@ class Chef
         is_server_2003
       end
 
+      def windows_nano_server?
+        return false unless windows?
+        require 'win32/registry'
+
+        # This method may be called before ohai runs (e.g., it may be used to
+        # determine settings in config.rb). Chef::Win32::Registry.new uses
+        # node attributes to verify the machine architecture which aren't
+        # accessible before ohai runs.
+        nano = nil
+        key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels"
+        access = ::Win32::Registry::KEY_QUERY_VALUE | 0x0100 # nano is 64-bit only
+        begin
+          ::Win32::Registry::HKEY_LOCAL_MACHINE.open(key, access) do |reg|
+            nano = reg["NanoServer"]
+          end
+        rescue ::Win32::Registry::Error
+          # If accessing the registry key failed, then we're probably not on
+          # nano. Fail through.
+        end
+        return nano == 1
+      end
+
+      def supports_msi?
+        return false unless windows?
+        require 'win32/registry'
+
+        key = "System\\CurrentControlSet\\Services\\msiserver"
+        access = ::Win32::Registry::KEY_QUERY_VALUE
+
+        begin
+          ::Win32::Registry::HKEY_LOCAL_MACHINE.open(key, access) do |reg|
+            true
+          end
+        rescue ::Win32::Registry::Error
+          false
+        end
+      end
+
+      def supports_powershell_execution_bypass?(node)
+        node[:languages] && node[:languages][:powershell] &&
+          node[:languages][:powershell][:version].to_i >= 3
+      end
+
       def supports_dsc?(node)
         node[:languages] && node[:languages][:powershell] &&
           node[:languages][:powershell][:version].to_i >= 4
       end
 
       def supports_dsc_invoke_resource?(node)
-        require 'rubygems'
         supports_dsc?(node) &&
-          Gem::Version.new(node[:languages][:powershell][:version]) >=
-            Gem::Version.new("5.0.10018.0")
+          supported_powershell_version?(node, "5.0.10018.0")
+      end
+
+      def supports_refresh_mode_enabled?(node)
+        supported_powershell_version?(node, "5.0.10586.0")
       end
+
+      def dsc_refresh_mode_disabled?(node)
+        require 'chef/util/powershell/cmdlet'
+        cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object)
+        metadata = cmdlet.run!.return_value
+        metadata['RefreshMode'] == 'Disabled'
+      end
+      
+
+      def supported_powershell_version?(node, version_string)
+        return false unless node[:languages] && node[:languages][:powershell]
+        require 'rubygems'
+        Gem::Version.new(node[:languages][:powershell][:version]) >=
+          Gem::Version.new(version_string)
+      end
+
     end
   end
 end
diff --git a/lib/chef/platform/rebooter.rb b/lib/chef/platform/rebooter.rb
index b46f0e3..b78ac38 100644
--- a/lib/chef/platform/rebooter.rb
+++ b/lib/chef/platform/rebooter.rb
@@ -32,7 +32,7 @@ class Chef
 
           cmd = if Chef::Platform.windows?
             # should this do /f as well? do we then need a minimum delay to let apps quit?
-            "shutdown /r /t #{reboot_info[:delay_mins]} /c \"#{reboot_info[:reason]}\""
+            "shutdown /r /t #{reboot_info[:delay_mins]*60} /c \"#{reboot_info[:reason]}\""
           else
             # probably Linux-only.
             "shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\""
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/platform/resource_handler_map.rb
similarity index 69%
copy from lib/chef/resource/whyrun_safe_ruby_block.rb
copy to lib/chef/platform/resource_handler_map.rb
index 6fa5383..27a7bb1 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/platform/resource_handler_map.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Phil Dibowitz (<phild at fb.com>)
-# Copyright:: Copyright (c) 2013 Facebook
+# Author:: John Keiser (<jkeiser at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,14 @@
 # limitations under the License.
 #
 
-class Chef
-  class Resource
-    class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :whyrun_safe_ruby_block
-      end
+require 'singleton'
+require 'chef/platform/handler_map'
 
+class Chef
+  class Platform
+    # @api private
+    class ResourceHandlerMap < Chef::Platform::HandlerMap
+      include Singleton
     end
   end
 end
diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb
index fc43b3e..5cc86fd 100644
--- a/lib/chef/platform/resource_priority_map.rb
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -1,37 +1,11 @@
+require 'singleton'
+require 'chef/platform/priority_map'
+
 class Chef
   class Platform
-    class ResourcePriorityMap
+    # @api private
+    class ResourcePriorityMap < Chef::Platform::PriorityMap
       include Singleton
-
-      def initialize
-        load_default_map
-      end
-
-      def get_priority_array(node, resource_name)
-        priority_map.get(node, resource_name.to_sym)
-      end
-
-      def set_priority_array(resource_name, priority_array, *filter)
-        priority resource_name.to_sym, priority_array.to_a, *filter
-      end
-
-      def priority(*args)
-        priority_map.set(*args)
-      end
-
-      private
-
-      def load_default_map
-        require 'chef/resources'
-
-        # MacOSX
-        priority :package, Chef::Resource::HomebrewPackage, os: "darwin"
-      end
-
-      def priority_map
-        require 'chef/node_map'
-        @priority_map ||= Chef::NodeMap.new
-      end
     end
   end
 end
diff --git a/lib/chef/platform/service_helpers.rb b/lib/chef/platform/service_helpers.rb
index dc0a808..ae95520 100644
--- a/lib/chef/platform/service_helpers.rb
+++ b/lib/chef/platform/service_helpers.rb
@@ -16,18 +16,12 @@
 # limitations under the License.
 #
 
-# XXX: mixing shellout into a mixin into classes has to be code smell
-require 'chef/mixin/shell_out'
-require 'chef/mixin/which'
+require 'chef/chef_class'
 
 class Chef
   class Platform
     class ServiceHelpers
       class << self
-
-        include Chef::Mixin::ShellOut
-        include Chef::Mixin::Which
-
         # This helper is mostly used to sort out the mess of different
         # linux mechanisms that can be used to start services.  It does
         # not necessarily need to linux-specific, but currently all our
@@ -42,60 +36,59 @@ class Chef
         # different services is NOT a design concern of this module.
         #
         def service_resource_providers
-          service_resource_providers = []
+          providers = []
 
-          if ::File.exist?("/usr/sbin/update-rc.d")
-            service_resource_providers << :debian
+          if ::File.exist?(Chef.path_to("/usr/sbin/update-rc.d"))
+            providers << :debian
           end
 
-          if ::File.exist?("/usr/sbin/invoke-rc.d")
-            service_resource_providers << :invokercd
+          if ::File.exist?(Chef.path_to("/usr/sbin/invoke-rc.d"))
+            providers << :invokercd
           end
 
-          if ::File.exist?("/sbin/insserv")
-            service_resource_providers << :insserv
+          if ::File.exist?(Chef.path_to("/sbin/initctl"))
+            providers << :upstart
           end
 
-          # debian >= 6.0 has /etc/init but does not have upstart
-          if ::File.exist?("/etc/init") && ::File.exist?("/sbin/start")
-            service_resource_providers << :upstart
+          if ::File.exist?(Chef.path_to("/sbin/insserv"))
+            providers << :insserv
           end
 
-          if ::File.exist?("/sbin/chkconfig")
-            service_resource_providers << :redhat
+          if systemd_is_init?
+            providers << :systemd
           end
 
-          if systemd_sanity_check?
-            service_resource_providers << :systemd
+          if ::File.exist?(Chef.path_to("/sbin/chkconfig"))
+            providers << :redhat
           end
 
-          service_resource_providers
+          providers
         end
 
         def config_for_service(service_name)
           configs = []
 
-          if ::File.exist?("/etc/init.d/#{service_name}")
+          if ::File.exist?(Chef.path_to("/etc/init.d/#{service_name}"))
             configs << :initd
           end
 
-          if ::File.exist?("/etc/init/#{service_name}.conf")
+          if ::File.exist?(Chef.path_to("/etc/init/#{service_name}.conf"))
             configs << :upstart
           end
 
-          if ::File.exist?("/etc/xinetd.d/#{service_name}")
+          if ::File.exist?(Chef.path_to("/etc/xinetd.d/#{service_name}"))
             configs << :xinetd
           end
 
-          if ::File.exist?("/etc/rc.d/#{service_name}")
+          if ::File.exist?(Chef.path_to("/etc/rc.d/#{service_name}"))
             configs << :etc_rcd
           end
 
-          if ::File.exist?("/usr/local/etc/rc.d/#{service_name}")
+          if ::File.exist?(Chef.path_to("/usr/local/etc/rc.d/#{service_name}"))
             configs << :usr_local_etc_rcd
           end
 
-          if systemd_sanity_check? && platform_has_systemd_unit?(service_name)
+          if has_systemd_service_unit?(service_name) || has_systemd_unit?(service_name)
             configs << :systemd
           end
 
@@ -104,37 +97,24 @@ class Chef
 
         private
 
-        def systemctl_path
-          if @systemctl_path.nil?
-            @systemctl_path = which("systemctl")
-          end
-          @systemctl_path
-        end
-
-        def systemd_sanity_check?
-          systemctl_path && File.exist?("/proc/1/comm") && File.open("/proc/1/comm").gets.chomp == "systemd"
+        def systemd_is_init?
+          ::File.exist?(Chef.path_to("/proc/1/comm")) &&
+            ::File.open(Chef.path_to("/proc/1/comm")).gets.chomp == "systemd" 
         end
 
-        def extract_systemd_services(command)
-          output = shell_out!(command).stdout
-          # first line finds e.g. "sshd.service"
-          services = []
-          output.each_line do |line|
-            fields = line.split
-            services << fields[0] if fields[1] == "loaded" || fields[1] == "not-found"
+        def has_systemd_service_unit?(svc_name)
+          %w( /etc /usr/lib /lib /run ).any? do |load_path|
+            ::File.exist?(
+              Chef.path_to("#{load_path}/systemd/system/#{svc_name.gsub(/@.*$/, '@')}.service")
+            )
           end
-          # this splits off the suffix after the last dot to return "sshd"
-          services += services.select {|s| s.match(/\.service$/) }.map { |s| s.sub(/(.*)\.service$/, '\1') }
-        rescue Mixlib::ShellOut::ShellCommandFailed
-          false
         end
 
-        def platform_has_systemd_unit?(service_name)
-          services = extract_systemd_services("#{systemctl_path} --all") +
-            extract_systemd_services("#{systemctl_path} list-unit-files")
-          services.include?(service_name)
-        rescue Mixlib::ShellOut::ShellCommandFailed
-          false
+        def has_systemd_unit?(svc_name)
+          # TODO: stop supporting non-service units with service resource
+          %w( /etc /usr/lib /lib /run ).any? do |load_path|
+            ::File.exist?(Chef.path_to("#{load_path}/systemd/system/#{svc_name}"))
+          end
         end
       end
     end
diff --git a/lib/chef/policy_builder.rb b/lib/chef/policy_builder.rb
index 136b285..56415db 100644
--- a/lib/chef/policy_builder.rb
+++ b/lib/chef/policy_builder.rb
@@ -18,6 +18,7 @@
 
 require 'chef/policy_builder/expand_node_object'
 require 'chef/policy_builder/policyfile'
+require 'chef/policy_builder/dynamic'
 
 class Chef
 
@@ -37,13 +38,5 @@ class Chef
   # * cookbook_hash is stored in run_context
   module PolicyBuilder
 
-    def self.strategy
-      if Chef::Config[:use_policyfile]
-        Policyfile
-      else
-        ExpandNodeObject
-      end
-    end
-
   end
 end
diff --git a/lib/chef/policy_builder/dynamic.rb b/lib/chef/policy_builder/dynamic.rb
new file mode 100644
index 0000000..c9842ba
--- /dev/null
+++ b/lib/chef/policy_builder/dynamic.rb
@@ -0,0 +1,186 @@
+#
+# Author:: Daniel DeLeo (<dan at chef.io>)
+# Copyright:: Copyright 2015 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 'forwardable'
+
+require 'chef/log'
+require 'chef/rest'
+require 'chef/run_context'
+require 'chef/config'
+require 'chef/node'
+require 'chef/exceptions'
+
+class Chef
+  module PolicyBuilder
+
+    # PolicyBuilder that selects either a Policyfile or non-Policyfile
+    # implementation based on the content of the node object.
+    class Dynamic
+
+      extend Forwardable
+
+      attr_reader :node
+      attr_reader :node_name
+      attr_reader :ohai_data
+      attr_reader :json_attribs
+      attr_reader :override_runlist
+      attr_reader :events
+
+      def initialize(node_name, ohai_data, json_attribs, override_runlist, events)
+        @implementation = nil
+
+        @node_name = node_name
+        @ohai_data = ohai_data
+        @json_attribs = json_attribs
+        @override_runlist = override_runlist
+        @events = events
+
+        @node = nil
+      end
+
+      ## PolicyBuilder API ##
+
+      # Loads the node state from the server, then picks the correct
+      # implementation class based on the node and json_attribs.
+      #
+      # Calls #finish_load_node on the implementation object to complete the
+      # loading process. All subsequent lifecycle calls are delegated.
+      #
+      # @return [Chef::Node] the loaded node.
+      def load_node
+        events.node_load_start(node_name, config)
+        Chef::Log.debug("Building node object for #{node_name}")
+
+        @node =
+          if Chef::Config[:solo]
+            Chef::Node.build(node_name)
+          else
+            Chef::Node.find_or_create(node_name)
+          end
+        select_implementation(node)
+        implementation.finish_load_node(node)
+        node
+      rescue Exception => e
+        events.node_load_failed(node_name, e, config)
+        raise
+      end
+
+      ## Delegated Public API Methods ##
+
+      ### Accessors ###
+
+      def_delegator :implementation, :original_runlist
+      def_delegator :implementation, :run_context
+      def_delegator :implementation, :run_list_expansion
+
+      ### Lifecycle Methods ###
+
+      # @!method build_node
+      #
+      # Applies external attributes (e.g., from JSON file, environment,
+      # policyfile, etc.) and determines the correct expanded run list for the
+      # run.
+      #
+      # @return [Chef::Node]
+      def_delegator :implementation, :build_node
+
+      # @!method setup_run_context
+      #
+      # Synchronizes cookbooks and initializes the run context object for the
+      # run.
+      #
+      # @return [Chef::RunContext]
+      def_delegator :implementation, :setup_run_context
+
+      # @!method expanded_run_list
+      #
+      # Resolves the run list to a form containing only recipes and sets the
+      # `roles` and `recipes` automatic attributes on the node.
+      #
+      # @return [#recipes, #roles] A RunListExpansion or duck-type.
+      def_delegator :implementation, :expand_run_list
+
+      # @!method sync_cookbooks
+      #
+      # Synchronizes cookbooks. In a normal chef-client run, this is handled by
+      # #setup_run_context, but may be called directly in some circumstances.
+      #
+      # @return [Hash{String => Chef::CookbookManifest}] A map of
+      #   CookbookManifest objects by cookbook name.
+      def_delegator :implementation, :sync_cookbooks
+
+      # @!method temporary_policy?
+      #
+      # Indicates whether the policy is temporary, which means an
+      # override_runlist was provided. Chef::Client uses this to decide whether
+      # to do the final node save at the end of the run or not.
+      #
+      # @return [true,false]
+      def_delegator :implementation, :temporary_policy?
+
+      ## Internal Public API ##
+
+      # Returns the selected implementation, or raises if not set. The
+      # implementation is set when #load_node is called.
+      #
+      # @return [PolicyBuilder::Policyfile, PolicyBuilder::ExpandNodeObject]
+      def implementation
+        @implementation or raise Exceptions::InvalidPolicybuilderCall, "#load_node must be called before other policy builder methods"
+      end
+
+      # @api private
+      #
+      # Sets the implementation based on the content of the node, node JSON
+      # (i.e., the `-j JSON_FILE` data), and config. This is only public for
+      # testing purposes; production code should call #load_node instead.
+      def select_implementation(node)
+        if policyfile_set_in_config? ||
+            policyfile_attribs_in_node_json? ||
+            node_has_policyfile_attrs?(node) ||
+            policyfile_compat_mode_config?
+          @implementation = Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events)
+        else
+          @implementation = ExpandNodeObject.new(node_name, ohai_data, json_attribs, override_runlist, events)
+        end
+      end
+
+      def config
+        Chef::Config
+      end
+
+      private
+
+      def node_has_policyfile_attrs?(node)
+        node.policy_name || node.policy_group
+      end
+
+      def policyfile_attribs_in_node_json?
+        json_attribs.key?("policy_name") || json_attribs.key?("policy_group")
+      end
+
+      def policyfile_set_in_config?
+        config[:use_policyfile] || config[:policy_name] || config[:policy_group]
+      end
+
+      def policyfile_compat_mode_config?
+        config[:deployment_group] && !config[:policy_document_native_api]
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb
index 524bdd9..848dd00 100644
--- a/lib/chef/policy_builder/expand_node_object.rb
+++ b/lib/chef/policy_builder/expand_node_object.rb
@@ -33,6 +33,9 @@ class Chef
     # expands the run_list on a node object and then queries the chef-server
     # to find the correct set of cookbooks, given version constraints of the
     # node's environment.
+    #
+    # Note that this class should only be used via PolicyBuilder::Dynamic and
+    # not instantiated directly.
     class ExpandNodeObject
 
       attr_reader :events
@@ -55,9 +58,10 @@ class Chef
         @run_list_expansion = nil
       end
 
-      # This method injects the run_context and provider and resource priority
-      # maps into the Chef class.  The run_context has to be injected here, the provider and
-      # resource maps could be moved if a better place can be found to do this work.
+      # This method injects the run_context and into the Chef class.
+      #
+      # NOTE: This is duplicated with the Policyfile implementation. If
+      # it gets any more complicated, it needs to be moved elsewhere.
       #
       # @param run_context [Chef::RunContext] the run_context to inject
       def setup_chef_class(run_context)
@@ -70,11 +74,13 @@ class Chef
           cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
           cl.load_cookbooks
           cookbook_collection = Chef::CookbookCollection.new(cl)
+          cookbook_collection.validate!
           run_context = Chef::RunContext.new(node, cookbook_collection, @events)
         else
           Chef::Cookbook::FileVendor.fetch_from_remote(api_service)
           cookbook_hash = sync_cookbooks
           cookbook_collection = Chef::CookbookCollection.new(cookbook_hash)
+          cookbook_collection.validate!
           run_context = Chef::RunContext.new(node, cookbook_collection, @events)
         end
 
@@ -93,25 +99,36 @@ class Chef
         run_context
       end
 
-
-      # In client-server operation, loads the node state from the server. In
-      # chef-solo operation, builds a new node object.
+      # DEPRECATED: As of Chef 12.5, chef selects either policyfile mode or
+      # "expand node" mode dynamically, based on the content of the node
+      # object, first boot JSON, and config. This happens in
+      # PolicyBuilder::Dynamic, which selects the implementation during
+      # #load_node and then delegates to either ExpandNodeObject or Policyfile
+      # implementations as appropriate. Tools authors should update their code
+      # to create a PolicyBuilder::Dynamc policy builder and allow it to select
+      # the proper implementation.
       def load_node
-        events.node_load_start(node_name, Chef::Config)
+        Chef.log_deprecation("ExpandNodeObject#load_node is deprecated. Please use Chef::PolicyBuilder::Dynamic instead of using ExpandNodeObject directly")
+
+        events.node_load_start(node_name, config)
         Chef::Log.debug("Building node object for #{node_name}")
 
-        if Chef::Config[:solo]
-          @node = Chef::Node.build(node_name)
-        else
-          @node = Chef::Node.find_or_create(node_name)
-        end
+        @node =
+          if Chef::Config[:solo]
+            Chef::Node.build(node_name)
+          else
+            Chef::Node.find_or_create(node_name)
+          end
+        finish_load_node(node)
+        node
       rescue Exception => e
-        # TODO: wrap this exception so useful error info can be given to the
-        # user.
-        events.node_load_failed(node_name, e, Chef::Config)
+        events.node_load_failed(node_name, e, config)
         raise
       end
 
+      def finish_load_node(node)
+        @node = node
+      end
 
       # Applies environment, external JSON attributes, and override run list to
       # the node, Then expands the run_list.
@@ -139,6 +156,7 @@ class Chef
         Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]")
 
         events.node_load_completed(node, @expanded_run_list_with_versions, Chef::Config)
+        events.run_list_expanded(@run_list_expansion)
 
         node
       end
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index ac25b54..3633110 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -68,22 +68,20 @@ class Chef
 
         @node = nil
 
-        Chef::Log.warn("Using experimental Policyfile feature")
-
         if Chef::Config[:solo]
-          raise UnsupportedFeature, "Policyfile does not support chef-solo at this time."
+          raise UnsupportedFeature, "Policyfile does not support chef-solo. Use chef-client local mode instead."
         end
 
         if override_runlist
-          raise UnsupportedFeature, "Policyfile does not support override run lists at this time"
+          raise UnsupportedFeature, "Policyfile does not support override run lists. Use named run_lists instead."
         end
 
         if json_attribs && json_attribs.key?("run_list")
-          raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data at this time"
+          raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data."
         end
 
         if Chef::Config[:environment] && !Chef::Config[:environment].chomp.empty?
-          raise UnsupportedFeature, "Policyfile does not work with Chef Environments"
+          raise UnsupportedFeature, "Policyfile does not work with Chef Environments."
         end
       end
 
@@ -112,17 +110,11 @@ class Chef
 
       ## PolicyBuilder API ##
 
-      # Loads the node state from the server.
-      def load_node
-        events.node_load_start(node_name, Chef::Config)
-        Chef::Log.debug("Building node object for #{node_name}")
-
-        @node = Chef::Node.find_or_create(node_name)
+      def finish_load_node(node)
+        @node = node
+        select_policy_name_and_group
         validate_policyfile
-        node
-      rescue Exception => e
-        events.node_load_failed(node_name, e, Chef::Config)
-        raise
+        events.policyfile_loaded(policy)
       end
 
       # Applies environment, external JSON attributes, and override run list to
@@ -153,25 +145,43 @@ class Chef
         raise
       end
 
+      # Synchronizes cookbooks and initializes the run context object for the
+      # run.
+      #
+      # @return [Chef::RunContext]
       def setup_run_context(specific_recipes=nil)
         Chef::Cookbook::FileVendor.fetch_from_remote(http_api)
         sync_cookbooks
         cookbook_collection = Chef::CookbookCollection.new(cookbooks_to_sync)
+        cookbook_collection.validate!
         run_context = Chef::RunContext.new(node, cookbook_collection, events)
 
+        setup_chef_class(run_context)
+
         run_context.load(run_list_expansion_ish)
 
+        setup_chef_class(run_context)
         run_context
       end
 
+      # Sets `run_list` on the node from the policy, sets `roles` and `recipes`
+      # attributes on the node accordingly.
+      #
+      # @return [RunListExpansionIsh] A RunListExpansion duck-type.
       def expand_run_list
+        CookbookCacheCleaner.instance.skip_removal = true if named_run_list_requested?
+
         node.run_list(run_list)
         node.automatic_attrs[:roles] = []
         node.automatic_attrs[:recipes] = run_list_expansion_ish.recipes
         run_list_expansion_ish
       end
 
-
+      # Synchronizes cookbooks. In a normal chef-client run, this is handled by
+      # #setup_run_context, but may be called directly in some circumstances.
+      #
+      # @return [Hash{String => Chef::CookbookManifest}] A map of
+      #   CookbookManifest objects by cookbook name.
       def sync_cookbooks
         Chef::Log.debug("Synchronizing cookbooks")
         synchronizer = Chef::CookbookSynchronizer.new(cookbooks_to_sync, events)
@@ -185,12 +195,18 @@ class Chef
 
       # Whether or not this is a temporary policy. Since PolicyBuilder doesn't
       # support override_runlist, this is always false.
+      #
+      # @return [false]
       def temporary_policy?
         false
       end
 
       ## Internal Public API ##
 
+      # @api private
+      #
+      # Generates an array of strings with recipe names including version and
+      # identifier info.
       def run_list_with_versions_for_display
         run_list.map do |recipe_spec|
           cookbook, recipe = parse_recipe_spec(recipe_spec)
@@ -200,6 +216,11 @@ class Chef
         end
       end
 
+      # @api private
+      #
+      # Sets up a RunListExpansionIsh object so that it can be used in place of
+      # a RunListExpansion object, to satisfy the API contract of
+      # #expand_run_list
       def run_list_expansion_ish
         recipes = run_list.map do |recipe_spec|
           cookbook, recipe = parse_recipe_spec(recipe_spec)
@@ -208,11 +229,15 @@ class Chef
         RunListExpansionIsh.new(recipes, [])
       end
 
+      # @api private
+      #
+      # Sets attributes from the policyfile on the node, using the role priority.
       def apply_policyfile_attributes
         node.attributes.role_default = policy["default_attributes"]
         node.attributes.role_override = policy["override_attributes"]
       end
 
+      # @api private
       def parse_recipe_spec(recipe_spec)
         rmatch = recipe_spec.match(/recipe\[([^:]+)::([^:]+)\]/)
         if rmatch.nil?
@@ -222,20 +247,31 @@ class Chef
         end
       end
 
+      # @api private
       def cookbook_lock_for(cookbook_name)
         cookbook_locks[cookbook_name]
       end
 
+      # @api private
       def run_list
-        policy["run_list"]
+        if named_run_list_requested?
+          named_run_list or
+            raise ConfigurationError,
+            "Policy '#{retrieved_policy_name}' revision '#{revision_id}' does not have named_run_list '#{named_run_list_name}'" +
+            "(available named_run_lists: [#{available_named_run_lists.join(', ')}])"
+        else
+          policy["run_list"]
+        end
       end
 
+      # @api private
       def policy
         @policy ||= http_api.get(policyfile_location)
       rescue Net::HTTPServerException => e
         raise ConfigurationError, "Error loading policyfile from `#{policyfile_location}': #{e.class} - #{e.message}"
       end
 
+      # @api private
       def policyfile_location
         if Chef::Config[:policy_document_native_api]
           validate_policy_config!
@@ -272,6 +308,7 @@ class Chef
         end
       end
 
+      # @api private
       def validate_recipe_spec(recipe_spec)
         parse_recipe_spec(recipe_spec)
         nil
@@ -281,11 +318,13 @@ class Chef
 
       class ConfigurationError < StandardError; end
 
+      # @api private
       def deployment_group
         Chef::Config[:deployment_group] or
           raise ConfigurationError, "Setting `deployment_group` is not configured."
       end
 
+      # @api private
       def validate_policy_config!
         policy_group or
           raise ConfigurationError, "Setting `policy_group` is not configured."
@@ -294,14 +333,75 @@ class Chef
           raise ConfigurationError, "Setting `policy_name` is not configured."
       end
 
+      # @api private
       def policy_group
         Chef::Config[:policy_group]
       end
 
+      # @api private
       def policy_name
         Chef::Config[:policy_name]
       end
 
+      # @api private
+      #
+      # Selects the `policy_name` and `policy_group` from the following sources
+      # in priority order:
+      #
+      # 1. JSON attribs (i.e., `-j JSON_FILE`)
+      # 2. `Chef::Config`
+      # 3. The node object
+      #
+      # The selected values are then copied to `Chef::Config` and the node.
+      def select_policy_name_and_group
+        policy_name_to_set =
+          policy_name_from_json_attribs ||
+          policy_name_from_config ||
+          policy_name_from_node
+
+        policy_group_to_set =
+          policy_group_from_json_attribs ||
+          policy_group_from_config ||
+          policy_group_from_node
+
+        node.policy_name = policy_name_to_set
+        node.policy_group = policy_group_to_set
+
+        Chef::Config[:policy_name] = policy_name_to_set
+        Chef::Config[:policy_group] = policy_group_to_set
+      end
+
+      # @api private
+      def policy_group_from_json_attribs
+        json_attribs["policy_group"]
+      end
+
+      # @api private
+      def policy_name_from_json_attribs
+        json_attribs["policy_name"]
+      end
+
+      # @api private
+      def policy_group_from_config
+        Chef::Config[:policy_group]
+      end
+
+      # @api private
+      def policy_name_from_config
+        Chef::Config[:policy_name]
+      end
+
+      # @api private
+      def policy_group_from_node
+        node.policy_group
+      end
+
+      # @api private
+      def policy_name_from_node
+        node.policy_name
+      end
+
+      # @api private
       # Builds a 'cookbook_hash' map of the form
       #   "COOKBOOK_NAME" => "IDENTIFIER"
       #
@@ -329,6 +429,7 @@ class Chef
         raise
       end
 
+      # @api private
       # Fetches the CookbookVersion object for the given name and identifer
       # specified in the lock_data.
       # TODO: This only implements Chef 11 compatibility mode, which means that
@@ -342,20 +443,58 @@ class Chef
         end
       end
 
+      # @api private
       def cookbook_locks
         policy["cookbook_locks"]
       end
 
+      # @api private
+      def revision_id
+        policy["revision_id"]
+      end
+
+      # @api private
       def http_api
         @api_service ||= Chef::REST.new(config[:chef_server_url])
       end
 
+      # @api private
       def config
         Chef::Config
       end
 
       private
 
+      # This method injects the run_context and into the Chef class.
+      #
+      # NOTE: This is duplicated with the ExpandNodeObject implementation. If
+      # it gets any more complicated, it needs to be moved elsewhere.
+      #
+      # @param run_context [Chef::RunContext] the run_context to inject
+      def setup_chef_class(run_context)
+        Chef.set_run_context(run_context)
+      end
+
+      def retrieved_policy_name
+        policy["name"]
+      end
+
+      def named_run_list
+        policy["named_run_lists"] && policy["named_run_lists"][named_run_list_name]
+      end
+
+      def available_named_run_lists
+        (policy["named_run_lists"] || {}).keys
+      end
+
+      def named_run_list_requested?
+        !!Chef::Config[:named_run_list]
+      end
+
+      def named_run_list_name
+        Chef::Config[:named_run_list]
+      end
+
       def compat_mode_manifest_for(cookbook_name, lock_data)
         xyz_version = lock_data["dotted_decimal_identifier"]
         rel_url = "cookbooks/#{cookbook_name}/#{xyz_version}"
diff --git a/lib/chef/property.rb b/lib/chef/property.rb
new file mode 100644
index 0000000..89e4ffe
--- /dev/null
+++ b/lib/chef/property.rb
@@ -0,0 +1,607 @@
+#
+# Author:: John Keiser <jkeiser at chef.io>
+# Copyright:: Copyright (c) 2015 John Keiser.
+# 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/exceptions'
+require 'chef/delayed_evaluator'
+require 'chef/chef_class'
+require 'chef/log'
+
+class Chef
+  #
+  # Type and validation information for a property on a resource.
+  #
+  # A property named "x" manipulates the "@x" instance variable on a
+  # resource.  The *presence* of the variable (`instance_variable_defined?(@x)`)
+  # tells whether the variable is defined; it may have any actual value,
+  # constrained only by validation.
+  #
+  # Properties may have validation, defaults, and coercion, and have full
+  # support for lazy values.
+  #
+  # @see Chef::Resource.property
+  # @see Chef::DelayedEvaluator
+  #
+  class Property
+    #
+    # Create a reusable property type that can be used in multiple properties
+    # in different resources.
+    #
+    # @param options [Hash<Symbol,Object>] Validation options. See Chef::Resource.property for
+    #   the list of options.
+    #
+    # @example
+    #   Property.derive(default: 'hi')
+    #
+    def self.derive(**options)
+      new(**options)
+    end
+
+    #
+    # Create a new property.
+    #
+    # @param options [Hash<Symbol,Object>] Property options, including
+    #   control options here, as well as validation options (see
+    #   Chef::Mixin::ParamsValidate#validate for a description of validation
+    #   options).
+    #   @option options [Symbol] :name The name of this property.
+    #   @option options [Class] :declared_in The class this property comes from.
+    #   @option options [Symbol] :instance_variable_name The instance variable
+    #     tied to this property. Must include a leading `@`. Defaults to `@<name>`.
+    #     `nil` means the property is opaque and not tied to a specific instance
+    #     variable.
+    #   @option options [Boolean] :desired_state `true` if this property is part of desired
+    #     state. Defaults to `true`.
+    #   @option options [Boolean] :identity `true` if this property is part of object
+    #     identity. Defaults to `false`.
+    #   @option options [Boolean] :name_property `true` if this
+    #     property defaults to the same value as `name`. Equivalent to
+    #     `default: lazy { name }`, except that #property_is_set? will
+    #     return `true` if the property is set *or* if `name` is set.
+    #   @option options [Object] :default The value this property
+    #     will return if the user does not set one. If this is `lazy`, it will
+    #     be run in the context of the instance (and able to access other
+    #     properties) and cached. If not, the value will be frozen with Object#freeze
+    #     to prevent users from modifying it in an instance.
+    #   @option options [Proc] :coerce A proc which will be called to
+    #     transform the user input to canonical form. The value is passed in,
+    #     and the transformed value returned as output. Lazy values will *not*
+    #     be passed to this method until after they are evaluated. Called in the
+    #     context of the resource (meaning you can access other properties).
+    #   @option options [Boolean] :required `true` if this property
+    #     must be present; `false` otherwise. This is checked after the resource
+    #     is fully initialized.
+    #
+    def initialize(**options)
+      options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) }
+
+      # Replace name_attribute with name_property
+      if options.has_key?(:name_attribute)
+        # If we have both name_attribute and name_property and they differ, raise an error
+        if options.has_key?(:name_property)
+          raise ArgumentError, "Cannot specify both name_property and name_attribute together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}."
+        end
+        # replace name_property with name_attribute in place
+        options = Hash[options.map { |k,v| k == :name_attribute ? [ :name_property, v ] : [ k,v ] }]
+      end
+
+      # Only pick the first of :default, :name_property and :name_attribute if
+      # more than one is specified.
+      if options.has_key?(:default) && options[:name_property]
+        if options[:default].nil? || options.keys.index(:name_property) < options.keys.index(:default)
+          options.delete(:default)
+          preferred_default = :name_property
+        else
+          options.delete(:name_property)
+          preferred_default = :default
+        end
+        Chef.log_deprecation("Cannot specify both default and name_property together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}. Only one (#{preferred_default}) will be obeyed. In Chef 13, this will become an error.")
+      end
+
+      @options = options
+
+      options[:name] = options[:name].to_sym if options[:name]
+      options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name]
+    end
+
+    def to_s
+      name
+    end
+
+    #
+    # The name of this property.
+    #
+    # @return [String]
+    #
+    def name
+      options[:name]
+    end
+
+    #
+    # The class this property was defined in.
+    #
+    # @return [Class]
+    #
+    def declared_in
+      options[:declared_in]
+    end
+
+    #
+    # The instance variable associated with this property.
+    #
+    # Defaults to `@<name>`
+    #
+    # @return [Symbol]
+    #
+    def instance_variable_name
+      if options.has_key?(:instance_variable_name)
+        options[:instance_variable_name]
+      elsif name
+        :"@#{name}"
+      end
+    end
+
+    #
+    # The raw default value for this resource.
+    #
+    # Does not coerce or validate the default. Does not evaluate lazy values.
+    #
+    # Defaults to `lazy { name }` if name_property is true; otherwise defaults to
+    # `nil`
+    #
+    def default
+      return options[:default] if options.has_key?(:default)
+      return Chef::DelayedEvaluator.new { name } if name_property?
+      nil
+    end
+
+    #
+    # Whether this is part of the resource's natural identity or not.
+    #
+    # @return [Boolean]
+    #
+    def identity?
+      options[:identity]
+    end
+
+    #
+    # Whether this is part of desired state or not.
+    #
+    # Defaults to true.
+    #
+    # @return [Boolean]
+    #
+    def desired_state?
+      return true if !options.has_key?(:desired_state)
+      options[:desired_state]
+    end
+
+    #
+    # Whether this is name_property or not.
+    #
+    # @return [Boolean]
+    #
+    def name_property?
+      options[:name_property]
+    end
+
+    #
+    # Whether this property has a default value.
+    #
+    # @return [Boolean]
+    #
+    def has_default?
+      options.has_key?(:default) || name_property?
+    end
+
+    #
+    # Whether this property is required or not.
+    #
+    # @return [Boolean]
+    #
+    def required?
+      options[:required]
+    end
+
+    #
+    # Validation options.  (See Chef::Mixin::ParamsValidate#validate.)
+    #
+    # @return [Hash<Symbol,Object>]
+    #
+    def validation_options
+      @validation_options ||= options.reject { |k,v|
+        [:declared_in,:name,:instance_variable_name,:desired_state,:identity,:default,:name_property,:coerce,:required].include?(k)
+      }
+    end
+
+    #
+    # Handle the property being called.
+    #
+    # The base implementation does the property get-or-set:
+    #
+    # ```ruby
+    # resource.myprop # get
+    # resource.myprop value # set
+    # ```
+    #
+    # Subclasses may implement this with any arguments they want, as long as
+    # the corresponding DSL calls it correctly.
+    #
+    # @param resource [Chef::Resource] The resource to get the property from.
+    # @param value The value to set (or NOT_PASSED if it is a get).
+    #
+    # @return The current value of the property. If it is a `set`, lazy values
+    #   will be returned without running, validating or coercing. If it is a
+    #   `get`, the non-lazy, coerced, validated value will always be returned.
+    #
+    def call(resource, value=NOT_PASSED)
+      if value == NOT_PASSED
+        return get(resource)
+      end
+
+      if value.nil? && !explicitly_accepts_nil?(resource)
+        # In Chef 12, value(nil) does a *get* instead of a set, so we
+        # warn if the value would have been changed. In Chef 13, it will be
+        # equivalent to value = nil.
+        result = get(resource)
+        if !result.nil?
+          Chef.log_deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.")
+        end
+        result
+      else
+        # Anything else, such as myprop(value) is a set
+        set(resource, value)
+      end
+    end
+
+    #
+    # Get the property value from the resource, handling lazy values,
+    # defaults, and validation.
+    #
+    # - If the property's value is lazy, it is evaluated, coerced and validated.
+    # - If the property has no value, and is required, raises ValidationFailed.
+    # - If the property has no value, but has a lazy default, it is evaluated,
+    #   coerced and validated. If the evaluated value is frozen, the resulting
+    # - If the property has no value, but has a default, the default value
+    #   will be returned and frozen. If the default value is lazy, it will be
+    #   evaluated, coerced and validated, and the result stored in the property.
+    # - If the property has no value, but is name_property, `resource.name`
+    #   is retrieved, coerced, validated and stored in the property.
+    # - Otherwise, `nil` is returned.
+    #
+    # @param resource [Chef::Resource] The resource to get the property from.
+    #
+    # @return The value of the property.
+    #
+    # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+    #   this property, or if the value is required and not set.
+    #
+    def get(resource)
+      if is_set?(resource)
+        value = get_value(resource)
+        if value.is_a?(DelayedEvaluator)
+          value = exec_in_resource(resource, value)
+          value = coerce(resource, value)
+          validate(resource, value)
+        end
+        value
+
+      else
+        # If the user does something like this:
+        #
+        # ```
+        # class MyResource < Chef::Resource
+        #   property :content
+        #   action :create do
+        #     file '/x.txt' do
+        #       content content
+        #     end
+        #   end
+        # end
+        # ```
+        #
+        # It won't do what they expect. This checks whether you try to *read*
+        # `content` while we are compiling the resource.
+        if resource.respond_to?(:resource_initializing) &&
+           resource.resource_initializing &&
+           resource.respond_to?(:enclosing_provider) &&
+           resource.enclosing_provider &&
+           resource.enclosing_provider.respond_to?(name)
+           Chef::Log.warn("#{Chef::Log.caller_location}: property #{name} is declared in both #{resource} and #{resource.enclosing_provider}. Use new_resource.#{name} instead. At #{Chef::Log.caller_location}")
+        end
+
+        if has_default?
+          value = default
+          if value.is_a?(DelayedEvaluator)
+            value = exec_in_resource(resource, value)
+          end
+
+          value = coerce(resource, value)
+
+          # We don't validate defaults
+
+          # If the value is mutable (non-frozen), we set it on the instance
+          # so that people can mutate it.  (All constant default values are
+          # frozen.)
+          if !value.frozen? && !value.nil?
+            set_value(resource, value)
+          end
+
+          value
+
+        elsif required?
+          raise Chef::Exceptions::ValidationFailed, "#{name} is required"
+        end
+      end
+    end
+
+    #
+    # Set the value of this property in the given resource.
+    #
+    # Non-lazy values are coerced and validated before being set. Coercion
+    # and validation of lazy values is delayed until they are first retrieved.
+    #
+    # @param resource [Chef::Resource] The resource to set this property in.
+    # @param value The value to set.
+    #
+    # @return The value that was set, after coercion (if lazy, still returns
+    #   the lazy value)
+    #
+    # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+    #   this property.
+    #
+    def set(resource, value)
+      unless value.is_a?(DelayedEvaluator)
+        value = coerce(resource, value)
+        validate(resource, value)
+      end
+      set_value(resource, value)
+    end
+
+    #
+    # Find out whether this property has been set.
+    #
+    # This will be true if:
+    # - The user explicitly set the value
+    # - The property has a default, and the value was retrieved.
+    #
+    # From this point of view, it is worth looking at this as "what does the
+    # user think this value should be." In order words, if the user grabbed
+    # the value, even if it was a default, they probably based calculations on
+    # it. If they based calculations on it and the value changes, the rest of
+    # the world gets inconsistent.
+    #
+    # @param resource [Chef::Resource] The resource to get the property from.
+    #
+    # @return [Boolean]
+    #
+    def is_set?(resource)
+      value_is_set?(resource)
+    end
+
+    #
+    # Reset the value of this property so that is_set? will return false and the
+    # default will be returned in the future.
+    #
+    # @param resource [Chef::Resource] The resource to get the property from.
+    #
+    def reset(resource)
+      reset_value(resource)
+    end
+
+    #
+    # Coerce an input value into canonical form for the property.
+    #
+    # After coercion, the value is suitable for storage in the resource.
+    # You must validate values after coercion, however.
+    #
+    # Does no special handling for lazy values.
+    #
+    # @param resource [Chef::Resource] The resource we're coercing against
+    #   (to provide context for the coerce).
+    # @param value The value to coerce.
+    #
+    # @return The coerced value.
+    #
+    # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+    #   this property.
+    #
+    def coerce(resource, value)
+      if options.has_key?(:coerce)
+        value = exec_in_resource(resource, options[:coerce], value)
+      end
+      value
+    end
+
+    #
+    # Validate a value.
+    #
+    # Calls Chef::Mixin::ParamsValidate#validate with #validation_options as
+    # options.
+    #
+    # @param resource [Chef::Resource] The resource we're validating against
+    #   (to provide context for the validate).
+    # @param value The value to validate.
+    #
+    # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+    #   this property.
+    #
+    def validate(resource, value)
+      resource.validate({ name => value }, { name => validation_options })
+    end
+
+    #
+    # Derive a new Property that is just like this one, except with some added or
+    # changed options.
+    #
+    # @param options [Hash<Symbol,Object>] List of options that would be passed
+    #   to #initialize.
+    #
+    # @return [Property] The new property type.
+    #
+    def derive(**modified_options)
+      # Since name_property, name_attribute and default override each other,
+      # if you specify one of them in modified_options it overrides anything in
+      # the original options.
+      options = self.options
+      if modified_options.has_key?(:name_property) ||
+         modified_options.has_key?(:name_attribute) ||
+         modified_options.has_key?(:default)
+        options = options.reject { |k,v| k == :name_attribute || k == :name_property || k == :default }
+      end
+      self.class.new(options.merge(modified_options))
+    end
+
+    #
+    # Emit the DSL for this property into the resource class (`declared_in`).
+    #
+    # Creates a getter and setter for the property.
+    #
+    def emit_dsl
+      # We don't create the getter/setter if it's a custom property; we will
+      # be using the existing getter/setter to manipulate it instead.
+      return if !instance_variable_name
+
+      # We prefer this form because the property name won't show up in the
+      # stack trace if you use `define_method`.
+      declared_in.class_eval <<-EOM, __FILE__, __LINE__+1
+        def #{name}(value=NOT_PASSED)
+          raise "Property #{name} of \#{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block_given?
+          self.class.properties[#{name.inspect}].call(self, value)
+        end
+        def #{name}=(value)
+          raise "Property #{name} of \#{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block_given?
+          self.class.properties[#{name.inspect}].set(self, value)
+        end
+      EOM
+    rescue SyntaxError
+      # If the name is not a valid ruby name, we use define_method.
+      declared_in.define_method(name) do |value=NOT_PASSED, &block|
+        raise "Property #{name} of #{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block
+        self.class.properties[name].call(self, value)
+      end
+      declared_in.define_method("#{name}=") do |value, &block|
+        raise "Property #{name} of #{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block
+        self.class.properties[name].set(self, value)
+      end
+    end
+
+    protected
+
+    #
+    # The options this Property will use for get/set behavior and validation.
+    #
+    # @see #initialize for a list of valid options.
+    #
+    attr_reader :options
+
+    #
+    # Find out whether this type accepts nil explicitly.
+    #
+    # A type accepts nil explicitly if "is" allows nil, it validates as nil, *and* is not simply
+    # an empty type.
+    #
+    # A type is presumed to accept nil if it does coercion (which must handle nil).
+    #
+    # These examples accept nil explicitly:
+    # ```ruby
+    # property :a, [ String, nil ]
+    # property :a, [ String, NilClass ]
+    # property :a, [ String, proc { |v| v.nil? } ]
+    # ```
+    #
+    # This does not (because the "is" doesn't exist or doesn't have nil):
+    #
+    # ```ruby
+    # property :x, String
+    # ```
+    #
+    # These do not, even though nil would validate fine (because they do not
+    # have "is"):
+    #
+    # ```ruby
+    # property :a
+    # property :a, equal_to: [ 1, 2, 3, nil ]
+    # property :a, kind_of: [ String, NilClass ]
+    # property :a, respond_to: [ ]
+    # property :a, callbacks: { "a" => proc { |v| v.nil? } }
+    # ```
+    #
+    # @param resource [Chef::Resource] The resource we're coercing against
+    #   (to provide context for the coerce).
+    #
+    # @return [Boolean] Whether this value explicitly accepts nil.
+    #
+    # @api private
+    def explicitly_accepts_nil?(resource)
+      options.has_key?(:coerce) ||
+      (options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false))
+    end
+
+    def get_value(resource)
+      if instance_variable_name
+        resource.instance_variable_get(instance_variable_name)
+      else
+        resource.send(name)
+      end
+    end
+
+    def set_value(resource, value)
+      if instance_variable_name
+        resource.instance_variable_set(instance_variable_name, value)
+      else
+        resource.send(name, value)
+      end
+    end
+
+    def value_is_set?(resource)
+      if instance_variable_name
+        resource.instance_variable_defined?(instance_variable_name)
+      else
+        true
+      end
+    end
+
+    def reset_value(resource)
+      if instance_variable_name
+        if value_is_set?(resource)
+          resource.remove_instance_variable(instance_variable_name)
+        end
+      else
+        raise ArgumentError, "Property #{name} has no instance variable defined and cannot be reset"
+      end
+    end
+
+    def exec_in_resource(resource, proc, *args)
+      if resource
+        if proc.arity > args.size
+          value = proc.call(resource, *args)
+        else
+          value = resource.instance_exec(*args, &proc)
+        end
+      else
+        value = proc.call
+      end
+
+      if value.is_a?(DelayedEvaluator)
+        value = coerce(resource, value)
+        validate(resource, value)
+      end
+      value
+    end
+  end
+end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 65a56cf..68bc8d7 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -22,14 +22,20 @@ require 'chef/mixin/convert_to_class_name'
 require 'chef/mixin/enforce_ownership_and_permissions'
 require 'chef/mixin/why_run'
 require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
 require 'chef/mixin/provides'
 require 'chef/platform/service_helpers'
 require 'chef/node_map'
+require 'forwardable'
 
 class Chef
   class Provider
+    require 'chef/mixin/why_run'
+    require 'chef/mixin/shell_out'
+    require 'chef/mixin/provides'
     include Chef::Mixin::WhyRun
     include Chef::Mixin::ShellOut
+    include Chef::Mixin::PowershellOut
     extend Chef::Mixin::Provides
 
     # supports the given resource and action (late binding)
@@ -60,6 +66,7 @@ class Chef
 
       @recipe_name = nil
       @cookbook_name = nil
+      self.class.include_resource_dsl_module(new_resource)
     end
 
     def whyrun_mode?
@@ -83,6 +90,9 @@ class Chef
       new_resource.cookbook_name
     end
 
+    def check_resource_semantics!
+    end
+
     def load_current_resource
       raise Chef::Exceptions::Override, "You must override load_current_resource in #{self.to_s}"
     end
@@ -108,12 +118,14 @@ class Chef
       # TODO: it would be preferable to get the action to be executed in the
       # constructor...
 
+      check_resource_semantics!
+
       # user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
-      if !whyrun_mode? || whyrun_supported?
+      if whyrun_mode? && !whyrun_supported?
+        events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
+      else
         load_current_resource
         events.resource_current_state_loaded(@new_resource, @action, @current_resource)
-      elsif whyrun_mode? && !whyrun_supported?
-        events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
       end
 
       define_resource_requirements
@@ -126,9 +138,7 @@ class Chef
       # we can't execute the action.
       # in non-whyrun mode, this will still cause the action to be
       # executed normally.
-      if whyrun_supported? && !requirements.action_blocked?(@action)
-        send("action_#{@action}")
-      elsif whyrun_mode?
+      if whyrun_mode? && (!whyrun_supported? || requirements.action_blocked?(@action))
         events.resource_bypassed(@new_resource, @action, self)
       else
         send("action_#{@action}")
@@ -165,6 +175,226 @@ class Chef
       converge_actions.add_action(descriptions, &block)
     end
 
+    #
+    # Handle patchy convergence safely.
+    #
+    # - Does *not* call the block if the current_resource's properties match
+    #   the properties the user specified on the resource.
+    # - Calls the block if current_resource does not exist
+    # - Calls the block if the user has specified any properties in the resource
+    #   whose values are *different* from current_resource.
+    # - Does *not* call the block if why-run is enabled (just prints out text).
+    # - Prints out automatic green text saying what properties have changed.
+    #
+    # @param properties An optional list of property names (symbols). If not
+    #   specified, `new_resource.class.state_properties` will be used.
+    # @param converge_block The block to do the converging in.
+    #
+    # @return [Boolean] whether the block was executed.
+    #
+    def converge_if_changed(*properties, &converge_block)
+      if !converge_block
+        raise ArgumentError, "converge_if_changed must be passed a block!"
+      end
+
+      properties = new_resource.class.state_properties.map { |p| p.name } if properties.empty?
+      properties = properties.map { |p| p.to_sym }
+      if current_resource
+        # Collect the list of modified properties
+        specified_properties = properties.select { |property| new_resource.property_is_set?(property) }
+        modified = specified_properties.select { |p| new_resource.send(p) != current_resource.send(p) }
+        if modified.empty?
+          properties_str = if sensitive
+            specified_properties.join(", ")
+          else
+            specified_properties.map { |p| "#{p}=#{new_resource.send(p).inspect}" }.join(", ")
+          end
+          Chef::Log.debug("Skipping update of #{new_resource.to_s}: has not changed any of the specified properties #{properties_str}.")
+          return false
+        end
+
+        # Print the pretty green text and run the block
+        property_size = modified.map { |p| p.size }.max
+        modified.map! do |p|
+          properties_str = if sensitive
+            '(suppressed sensitive property)'
+          else
+            "#{new_resource.send(p).inspect} (was #{current_resource.send(p).inspect})"
+          end
+          "  set #{p.to_s.ljust(property_size)} to #{properties_str}"
+        end
+        converge_by([ "update #{current_resource.identity}" ] + modified, &converge_block)
+
+      else
+        # The resource doesn't exist. Mark that we are *creating* this, and
+        # write down any properties we are setting.
+        property_size = properties.map { |p| p.size }.max
+        created = properties.map do |property|
+          default = ' (default value)' unless new_resource.property_is_set?(property)
+          properties_str = if sensitive
+            '(suppressed sensitive property)'
+          else
+            new_resource.send(property).inspect
+          end
+          "  set #{property.to_s.ljust(property_size)} to #{properties_str}#{default}"
+        end
+
+        converge_by([ "create #{new_resource.identity}" ] + created, &converge_block)
+      end
+      true
+    end
+
+    def self.provides(short_name, opts={}, &block)
+      Chef.provider_handler_map.set(short_name, self, opts, &block)
+    end
+
+    def self.provides?(node, resource)
+      Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self)
+    end
+
+    #
+    # Include attributes, public and protected methods from this Resource in
+    # the provider.
+    #
+    # If this is set to true, delegate methods are included in the provider so
+    # that you can call (for example) `attrname` and it will call
+    # `new_resource.attrname`.
+    #
+    # The actual include does not happen until the first time the Provider
+    # is instantiated (so that we don't have to worry about load order issues).
+    #
+    # @param include_resource_dsl [Boolean] Whether to include resource DSL or
+    #   not (defaults to `false`).
+    #
+    def self.include_resource_dsl(include_resource_dsl)
+      @include_resource_dsl = include_resource_dsl
+    end
+
+    # Create the resource DSL module that forwards resource methods to new_resource
+    #
+    # @api private
+    def self.include_resource_dsl_module(resource)
+      if @include_resource_dsl && !defined?(@included_resource_dsl_module)
+        provider_class = self
+        @included_resource_dsl_module = Module.new do
+          extend Forwardable
+          define_singleton_method(:to_s) { "forwarder module for #{provider_class}" }
+          define_singleton_method(:inspect) { to_s }
+          # Add a delegator for each explicit property that will get the *current* value
+          # of the property by default instead of the *actual* value.
+          resource.class.properties.each do |name, property|
+            class_eval(<<-EOM, __FILE__, __LINE__)
+              def #{name}(*args, &block)
+                # If no arguments were passed, we process "get" by defaulting
+                # the value to current_resource, not new_resource. This helps
+                # avoid issues where resources accidentally overwrite perfectly
+                # valid stuff with default values.
+                if args.empty? && !block
+                  if !new_resource.property_is_set?(__method__) && current_resource
+                    return current_resource.public_send(__method__)
+                  end
+                end
+                new_resource.public_send(__method__, *args, &block)
+              end
+            EOM
+          end
+          dsl_methods =
+             resource.class.public_instance_methods +
+             resource.class.protected_instance_methods -
+             provider_class.instance_methods -
+             resource.class.properties.keys
+          def_delegators(:new_resource, *dsl_methods)
+        end
+        include @included_resource_dsl_module
+      end
+    end
+
+    # Enables inline evaluation of resources in provider actions.
+    #
+    # Without this option, any resources declared inside the Provider are added
+    # to the resource collection after the current position at the time the
+    # action is executed. Because they are added to the primary resource
+    # collection for the chef run, they can notify other resources outside
+    # the Provider, and potentially be notified by resources outside the Provider
+    # (but this is complicated by the fact that they don't exist until the
+    # provider executes). In this mode, it is impossible to correctly set the
+    # updated_by_last_action flag on the parent Provider resource, since it
+    # executes and returns before its component resources are run.
+    #
+    # With this option enabled, each action creates a temporary run_context
+    # with its own resource collection, evaluates the action's code in that
+    # context, and then converges the resources created. If any resources
+    # were updated, then this provider's new_resource will be marked updated.
+    #
+    # In this mode, resources created within the Provider cannot interact with
+    # external resources via notifies, though notifications to other
+    # resources within the Provider will work. Delayed notifications are executed
+    # at the conclusion of the provider's action, *not* at the end of the
+    # main chef run.
+    #
+    # This mode of evaluation is experimental, but is believed to be a better
+    # set of tradeoffs than the append-after mode, so it will likely become
+    # the default in a future major release of Chef.
+    #
+    def self.use_inline_resources
+      extend InlineResources::ClassMethods
+      include InlineResources
+    end
+
+    # Chef::Provider::InlineResources
+    # Implementation of inline resource convergence for providers. See
+    # Provider.use_inline_resources for a longer explanation.
+    #
+    # This code is restricted to a module so that it can be selectively
+    # applied to providers on an opt-in basis.
+    #
+    # @api private
+    module InlineResources
+
+      # Create a child run_context, compile the block, and converge it.
+      #
+      # @api private
+      def compile_and_converge_action(&block)
+        old_run_context = run_context
+        @run_context = run_context.create_child
+        return_value = instance_eval(&block)
+        Chef::Runner.new(run_context).converge
+        return_value
+      ensure
+        if run_context.resource_collection.any? { |r| r.updated? }
+          new_resource.updated_by_last_action(true)
+        end
+        @run_context = old_run_context
+      end
+
+      # Class methods for InlineResources. Overrides the `action` DSL method
+      # with one that enables inline resource convergence.
+      #
+      # @api private
+      module ClassMethods
+        # Defines an action method on the provider, running the block to
+        # compile the resources, converging them, and then checking if any
+        # were updated (and updating new-resource if so)
+        def action(name, &block)
+          # We need the block directly in a method so that `super` works
+          define_method("compile_action_#{name}", &block)
+          # We try hard to use `def` because define_method doesn't show the method name in the stack.
+          begin
+            class_eval <<-EOM
+              def action_#{name}
+                compile_and_converge_action { compile_action_#{name} }
+              end
+            EOM
+          rescue SyntaxError
+            define_method("action_#{name}") { send("compile_action_#{name}") }
+          end
+        end
+      end
+
+      require 'chef/dsl/recipe'
+      include Chef::DSL::Recipe::FullDSL
+    end
+
     protected
 
     def converge_actions
@@ -182,14 +412,50 @@ class Chef
       # manipulating notifies.
 
       converge_by ("evaluate block and run any associated actions") do
-        saved_run_context = @run_context
-        @run_context = @run_context.dup
-        @run_context.resource_collection = Chef::ResourceCollection.new
-        instance_eval(&block)
-        Chef::Runner.new(@run_context).converge
-        @run_context = saved_run_context
+        saved_run_context = run_context
+        begin
+          @run_context = run_context.create_child
+          instance_eval(&block)
+          Chef::Runner.new(run_context).converge
+        ensure
+          @run_context = saved_run_context
+        end
       end
     end
 
+    module DeprecatedLWRPClass
+      def const_missing(class_name)
+        if deprecated_constants[class_name.to_sym]
+          Chef.log_deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed.  Use Chef::ProviderResolver.new(node, resource, action) instead.")
+          deprecated_constants[class_name.to_sym]
+        else
+          raise NameError, "uninitialized constant Chef::Provider::#{class_name}"
+        end
+      end
+
+      # @api private
+      def register_deprecated_lwrp_class(provider_class, class_name)
+        # Register Chef::Provider::MyProvider with deprecation warnings if you
+        # try to access it
+        if Chef::Provider.const_defined?(class_name, false)
+          Chef::Log.warn "Chef::Provider::#{class_name} already exists!  Cannot create deprecation class for #{provider_class}"
+        else
+          deprecated_constants[class_name.to_sym] = provider_class
+        end
+      end
+
+      private
+
+      def deprecated_constants
+        @deprecated_constants ||= {}
+      end
+    end
+    extend DeprecatedLWRPClass
   end
 end
+
+# Requiring things at the bottom breaks cycles
+require 'chef/chef_class'
+require 'chef/mixin/why_run'
+require 'chef/resource_collection'
+require 'chef/runner'
diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb
index b6b386e..5f01344 100644
--- a/lib/chef/provider/batch.rb
+++ b/lib/chef/provider/batch.rb
@@ -28,6 +28,14 @@ class Chef
         super(new_resource, run_context, '.bat')
       end
 
+      def command
+        basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+
+        interpreter_path = Chef::Util::PathHelper.join(basepath, interpreter)
+
+        "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\""
+      end
+
       def flags
         @new_resource.flags.nil? ? '/c' : new_resource.flags + ' /c'
       end
diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb
index 0750c04..01c61e4 100644
--- a/lib/chef/provider/cron/unix.rb
+++ b/lib/chef/provider/cron/unix.rb
@@ -20,6 +20,7 @@
 
 require 'chef/log'
 require 'chef/provider'
+require 'chef/provider/cron'
 
 class Chef
   class Provider
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index 19e7c01..c59200e 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -201,7 +201,7 @@ class Chef
 
           converge_by("execute migration command #{@new_resource.migration_command}") do
             Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}"
-            run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path, :log_level => :info))
+            shell_out!(@new_resource.migration_command,run_options(:cwd=>release_path, :log_level => :info))
           end
         end
       end
@@ -221,7 +221,7 @@ class Chef
           else
             converge_by("restart app using command #{@new_resource.restart_command}") do
               Chef::Log.info("#{@new_resource} restarting app")
-              run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path))
+              shell_out!(@new_resource.restart_command,run_options(:cwd=>@new_resource.current_path))
             end
           end
         end
@@ -276,7 +276,7 @@ class Chef
 
       def enforce_ownership
         converge_by("force ownership of #{@new_resource.deploy_to} to #{@new_resource.group}:#{@new_resource.user}") do
-          FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to)
+          FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to, :force => true)
           Chef::Log.info("#{@new_resource} set user to #{@new_resource.user}") if @new_resource.user
           Chef::Log.info("#{@new_resource} set group to #{@new_resource.group}") if @new_resource.group
         end
@@ -373,11 +373,9 @@ class Chef
       end
 
       def gem_resource_collection_runner
-        gems_collection = Chef::ResourceCollection.new
-        gem_packages.each { |rbgem| gems_collection.insert(rbgem) }
-        gems_run_context = run_context.dup
-        gems_run_context.resource_collection = gems_collection
-        Chef::Runner.new(gems_run_context)
+        child_context = run_context.create_child
+        gem_packages.each { |rbgem| child_context.resource_collection.insert(rbgem) }
+        Chef::Runner.new(child_context)
       end
 
       def gem_packages
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 416393a..8892d3a 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -43,6 +43,9 @@ class Chef
       end
 
       def define_resource_requirements
+        # deep inside FAC we have to assert requirements, so call FACs hook to set that up
+        access_controls.define_resource_requirements
+
         requirements.assert(:create) do |a|
           # Make sure the parent dir exists, or else fail.
           # for why run, print a message explaining the potential error.
@@ -61,7 +64,13 @@ class Chef
               is_parent_writable = lambda do |base_dir|
                 base_dir = ::File.dirname(base_dir)
                 if ::File.exists?(base_dir)
-                  Chef::FileAccessControl.writable?(base_dir)
+                  if Chef::FileAccessControl.writable?(base_dir)
+                    true
+                  elsif Chef::Util::PathHelper.is_sip_path?(base_dir, node)
+                    Chef::Util::PathHelper.writable_sip_path?(base_dir)
+                  else
+                    false
+                  end
                 else
                   is_parent_writable.call(base_dir)
                 end
@@ -71,7 +80,13 @@ class Chef
               # in why run mode & parent directory does not exist no permissions check is required
               # If not in why run, permissions must be valid and we rely on prior assertion that dir exists
               if !whyrun_mode? || ::File.exists?(parent_directory)
-                Chef::FileAccessControl.writable?(parent_directory)
+                if Chef::FileAccessControl.writable?(parent_directory)
+                  true
+                elsif Chef::Util::PathHelper.is_sip_path?(parent_directory, node)
+                  Chef::Util::PathHelper.writable_sip_path?(@new_resource.path)
+                else
+                  false
+                end
               else
                 true
               end
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 2812c15..fd25a14 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -15,7 +15,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
 require 'chef/util/powershell/cmdlet'
 require 'chef/util/dsc/local_configuration_manager'
 require 'chef/mixin/powershell_type_coercions'
@@ -25,19 +24,19 @@ class Chef
   class Provider
     class DscResource < Chef::Provider
       include Chef::Mixin::PowershellTypeCoercions
-
       provides :dsc_resource, os: "windows"
-
       def initialize(new_resource, run_context)
         super
         @new_resource = new_resource
         @module_name = new_resource.module_name
+        @reboot_resource = nil
       end
 
       def action_run
         if ! test_resource
           converge_by(generate_description) do
             result = set_resource
+            reboot_if_required
           end
         end
       end
@@ -53,17 +52,16 @@ class Chef
         requirements.assert(:run) do |a|
           a.assertion { supports_dsc_invoke_resource? }
           err = ["You must have Powershell version >= 5.0.10018.0 to use dsc_resource."]
-          a.failure_message Chef::Exceptions::NoProviderAvailable,
+          a.failure_message Chef::Exceptions::ProviderNotFound,
             err
           a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."]
           a.block_action!
         end
         requirements.assert(:run) do |a|
-          a.assertion {
-            meta_configuration['RefreshMode'] == 'Disabled'
-          }
-          err = ["The LCM must have its RefreshMode set to Disabled. "]
-          a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+          a.assertion { supports_refresh_mode_enabled? || dsc_refresh_mode_disabled? }
+          err = ["The LCM must have its RefreshMode set to Disabled for" \
+                 " PowerShell versions before 5.0.10586.0."]
+          a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
           a.whyrun err + ["Assuming a previous resource sets the RefreshMode."]
           a.block_action!
         end
@@ -86,6 +84,14 @@ class Chef
         run_context && Chef::Platform.supports_dsc_invoke_resource?(node)
       end
 
+      def dsc_refresh_mode_disabled?
+        Chef::Platform.dsc_refresh_mode_disabled?(node)
+      end
+
+      def supports_refresh_mode_enabled?
+        Chef::Platform.supports_refresh_mode_enabled?(node)
+      end
+
       def generate_description
         @converge_description
       end
@@ -97,7 +103,6 @@ class Chef
       def module_name
         @module_name ||= begin
           found = resource_store.find(dsc_resource_name)
-
           r = case found.length
               when 0
                 raise Chef::Exceptions::ResourceNotFound,
@@ -117,15 +122,23 @@ class Chef
 
       def test_resource
         result = invoke_resource(:test)
+        @converge_description = result.stream(:verbose)
+
         # We really want this information from the verbose stream,
-        # however Invoke-DscResource is not correctly writing to that
-        # stream and instead just dumping to stdout
-        @converge_description = result.stdout
-        result.return_value[0]["InDesiredState"]
+        # however in some versions of WMF, Invoke-DscResource is not correctly
+        # writing to that stream and instead just dumping to stdout
+        if @converge_description.empty?
+          @converge_description = result.stdout
+        end
+
+        return_dsc_resource_result(result, "InDesiredState")
       end
 
       def set_resource
         result = invoke_resource(:set)
+        if return_dsc_resource_result(result, 'RebootRequired')
+          create_reboot_resource
+        end
         result.return_value
       end
 
@@ -133,25 +146,49 @@ class Chef
         properties = translate_type(@new_resource.properties)
         switches = "-Method #{method.to_s} -Name #{@new_resource.resource}"\
                    " -Property #{properties} -Verbose"
-
         if module_name != :none
           switches += " -Module #{module_name}"
         end
-
         cmdlet = Chef::Util::Powershell::Cmdlet.new(
           node,
           "Invoke-DscResource #{switches}",
           output_format
         )
-        cmdlet.run!
+        cmdlet.run!({}, {:timeout => new_resource.timeout})
       end
 
-      def meta_configuration
-        cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object)
-        result = cmdlet.run!
-        result.return_value
+      def return_dsc_resource_result(result, property_name)
+        if result.return_value.is_a?(Array)
+          # WMF Feb 2015 Preview
+          result.return_value[0][property_name]
+        else
+          # WMF April 2015 Preview
+          result.return_value[property_name]
+        end
       end
 
+      def create_reboot_resource
+        @reboot_resource = Chef::Resource::Reboot.new(
+          "Reboot for #{@new_resource.name}",
+          run_context
+        ).tap do |r|
+          r.reason("Reboot for #{@new_resource.resource}.")
+        end
+      end
+
+      def reboot_if_required
+        reboot_action = @new_resource.reboot_action
+        unless @reboot_resource.nil?
+          case reboot_action
+          when :nothing
+            Chef::Log.debug("A reboot was requested by the DSC resource, but reboot_action is :nothing.")
+            Chef::Log.debug("This dsc_resource will not reboot the node.")
+          else
+            Chef::Log.debug("Requesting node reboot with #{reboot_action}.")
+            @reboot_resource.run_action(reboot_action)
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb
index a75e68a..b243213 100644
--- a/lib/chef/provider/dsc_script.rb
+++ b/lib/chef/provider/dsc_script.rb
@@ -70,7 +70,7 @@ class Chef
             "Powershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource.",
           ]
           a.assertion { supports_dsc? }
-          a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+          a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
           a.whyrun err + ["Assuming a previous resource installs Powershell 4.0 or higher."]
           a.block_action!
         end
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb
index b44112c..200beb0 100644
--- a/lib/chef/provider/execute.rb
+++ b/lib/chef/provider/execute.rb
@@ -41,7 +41,7 @@ class Chef
       def define_resource_requirements
          # @todo: this should change to raise in some appropriate major version bump.
          if creates && creates_relative? && !cwd
-           Chef::Log.warn "Providing a relative path for the creates attribute without the cwd is deprecated and will be changed to fail (CHEF-3819)"
+           Chef::Log.warn "Providing a relative path for the creates attribute without the cwd is deprecated and will be changed to fail in the future (CHEF-3819)"
          end
       end
 
@@ -58,7 +58,16 @@ class Chef
         end
 
         converge_by("execute #{description}") do
-          result = shell_out!(command, opts)
+          begin
+            shell_out!(command, opts)
+          rescue Mixlib::ShellOut::ShellCommandFailed
+            if sensitive?
+              raise Mixlib::ShellOut::ShellCommandFailed,
+                "Command execution failed. STDOUT/STDERR suppressed for sensitive resource"
+            else
+              raise
+            end
+          end
           Chef::Log.info("#{new_resource} ran successfully")
         end
       end
@@ -69,6 +78,14 @@ class Chef
         !!new_resource.sensitive
       end
 
+      def live_stream?
+        Chef::Config[:stream_execute_output] || !!new_resource.live_stream
+      end
+
+      def stream_to_stdout?
+        STDOUT.tty? && !Chef::Config[:daemon]
+      end
+
       def opts
         opts = {}
         opts[:timeout]     = timeout
@@ -80,8 +97,12 @@ class Chef
         opts[:umask]       = umask if umask
         opts[:log_level]   = :info
         opts[:log_tag]     = new_resource.to_s
-        if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? && !sensitive?
-          opts[:live_stream] = STDOUT
+        if (Chef::Log.info? || live_stream?) && !sensitive?
+          if run_context.events.formatter?
+            opts[:live_stream] = Chef::EventDispatch::EventsOutputStream.new(run_context.events, :name => :execute)
+          elsif stream_to_stdout?
+            opts[:live_stream] = STDOUT
+          end
         end
         opts
       end
diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb
index c070d29..5ed7c6a 100644
--- a/lib/chef/provider/file.rb
+++ b/lib/chef/provider/file.rb
@@ -26,8 +26,10 @@ require 'fileutils'
 require 'chef/scan_access_control'
 require 'chef/mixin/checksum'
 require 'chef/mixin/file_class'
+require 'chef/mixin/enforce_ownership_and_permissions'
 require 'chef/util/backup'
 require 'chef/util/diff'
+require 'chef/util/selinux'
 require 'chef/deprecation/provider/file'
 require 'chef/deprecation/warnings'
 require 'chef/file_content_management/deploy'
@@ -386,10 +388,11 @@ class Chef
 
       def update_file_contents
         do_backup unless needs_creating?
-        deployment_strategy.deploy(tempfile.path, ::File.realpath(@new_resource.path))
-        Chef::Log.info("#{@new_resource} updated file contents #{@new_resource.path}")
+        deployment_strategy.deploy(tempfile.path, ::File.realpath(new_resource.path))
+        Chef::Log.info("#{new_resource} updated file contents #{new_resource.path}")
         if managing_content?
-          @new_resource.checksum(checksum(@new_resource.path)) # for reporting
+          # save final checksum for reporting.
+          new_resource.final_checksum = checksum(new_resource.path)
         end
       end
 
diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb
index a802758..a1cf920 100644
--- a/lib/chef/provider/group.rb
+++ b/lib/chef/provider/group.rb
@@ -125,7 +125,7 @@ class Chef
       def action_create
         case @group_exists
         when false
-          converge_by("create #{@new_resource.group_name}") do
+          converge_by("create group #{@new_resource.group_name}") do
             create_group
             Chef::Log.info("#{@new_resource} created")
           end
diff --git a/lib/chef/provider/group/aix.rb b/lib/chef/provider/group/aix.rb
index 6ac9d03..92bb8cb 100644
--- a/lib/chef/provider/group/aix.rb
+++ b/lib/chef/provider/group/aix.rb
@@ -23,6 +23,7 @@ class Chef
   class Provider
     class Group
       class Aix < Chef::Provider::Group::Groupadd
+        provides :group, platform: 'aix'
 
         def required_binaries
           [ "/usr/bin/mkgroup",
diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb
index d7e8f2e..9775ac8 100644
--- a/lib/chef/provider/group/dscl.rb
+++ b/lib/chef/provider/group/dscl.rb
@@ -21,7 +21,7 @@ class Chef
     class Group
       class Dscl < Chef::Provider::Group
 
-        provides :group, os: "darwin"
+        provides :group, os: 'darwin'
 
         def dscl(*args)
           host = "."
diff --git a/lib/chef/provider/group/gpasswd.rb b/lib/chef/provider/group/gpasswd.rb
index 521affa..432c524 100644
--- a/lib/chef/provider/group/gpasswd.rb
+++ b/lib/chef/provider/group/gpasswd.rb
@@ -22,6 +22,7 @@ class Chef
   class Provider
     class Group
       class Gpasswd < Chef::Provider::Group::Groupadd
+        provides :group
 
         def load_current_resource
           super
diff --git a/lib/chef/provider/group/groupmod.rb b/lib/chef/provider/group/groupmod.rb
index f929954..82b68b8 100644
--- a/lib/chef/provider/group/groupmod.rb
+++ b/lib/chef/provider/group/groupmod.rb
@@ -21,7 +21,7 @@ class Chef
     class Group
       class Groupmod < Chef::Provider::Group
 
-        provides :group, os: "netbsd"
+        provides :group, os: 'netbsd'
 
         def load_current_resource
           super
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index 7a66ab4..5b5c813 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -20,6 +20,7 @@ class Chef
   class Provider
     class Group
       class Pw < Chef::Provider::Group
+        provides :group, platform: 'freebsd'
 
         def load_current_resource
           super
@@ -108,7 +109,7 @@ class Chef
           else
             # Append is not set so we're resetting the membership of
             # the group to the given members.
-            members_to_be_added = @new_resource.members
+            members_to_be_added = @new_resource.members.dup
             @current_resource.members.each do |member|
               # No need to re-add a member if it's present in the new
               # list of members
diff --git a/lib/chef/provider/group/suse.rb b/lib/chef/provider/group/suse.rb
index 7ac2831..b47ea33 100644
--- a/lib/chef/provider/group/suse.rb
+++ b/lib/chef/provider/group/suse.rb
@@ -22,6 +22,8 @@ class Chef
   class Provider
     class Group
       class Suse < Chef::Provider::Group::Groupadd
+        provides :group, platform: 'opensuse', platform_version: '< 12.3'
+        provides :group, platform: 'suse', platform_version: '< 12.0'
 
         def load_current_resource
           super
diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb
index e50e13c..d78d42d 100644
--- a/lib/chef/provider/group/usermod.rb
+++ b/lib/chef/provider/group/usermod.rb
@@ -23,7 +23,8 @@ class Chef
     class Group
       class Usermod < Chef::Provider::Group::Groupadd
 
-        provides :group, os: "openbsd"
+        provides :group, os: %w(openbsd solaris2 hpux)
+        provides :group, platform: "opensuse"
 
         def load_current_resource
           super
diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb
index 54e49b0..46d8afc 100644
--- a/lib/chef/provider/group/windows.rb
+++ b/lib/chef/provider/group/windows.rb
@@ -26,7 +26,7 @@ class Chef
     class Group
       class Windows < Chef::Provider::Group
 
-        provides :group, os: "windows"
+        provides :group, os: 'windows'
 
         def initialize(new_resource,run_context)
           super
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 06080c9..7869917 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -39,6 +39,8 @@ require 'erb'
 class Chef
   class Provider
     class Ifconfig < Chef::Provider
+      provides :ifconfig
+
       include Chef::Mixin::ShellOut
       include Chef::Mixin::Command
 
@@ -192,7 +194,7 @@ class Chef
 
       private
       def add_command
-        command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+        command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
         command << " netmask #{@new_resource.mask}" if @new_resource.mask
         command << " metric #{@new_resource.metric}" if @new_resource.metric
         command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
@@ -200,7 +202,7 @@ class Chef
       end
 
       def enable_command
-        command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+        command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
         command << " netmask #{@new_resource.mask}" if @new_resource.mask
         command << " metric #{@new_resource.metric}" if @new_resource.metric
         command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
diff --git a/lib/chef/provider/ifconfig/aix.rb b/lib/chef/provider/ifconfig/aix.rb
index 8fead44..25c3de3 100644
--- a/lib/chef/provider/ifconfig/aix.rb
+++ b/lib/chef/provider/ifconfig/aix.rb
@@ -22,6 +22,7 @@ class Chef
   class Provider
     class Ifconfig
       class Aix < Chef::Provider::Ifconfig
+        provides :ifconfig, platform: %w(aix)
 
         def load_current_resource
           @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
diff --git a/lib/chef/provider/ifconfig/debian.rb b/lib/chef/provider/ifconfig/debian.rb
index 7589971..1e6863c 100644
--- a/lib/chef/provider/ifconfig/debian.rb
+++ b/lib/chef/provider/ifconfig/debian.rb
@@ -23,6 +23,8 @@ class Chef
   class Provider
     class Ifconfig
       class Debian < Chef::Provider::Ifconfig
+        provides :ifconfig, platform: %w(ubuntu), platform_version: '>= 11.10'
+        provides :ifconfig, platform: %w(debian), platform_version: '>= 7.0'
 
         INTERFACES_FILE = "/etc/network/interfaces"
         INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d"
diff --git a/lib/chef/provider/ifconfig/redhat.rb b/lib/chef/provider/ifconfig/redhat.rb
index ef35b0e..ee053d1 100644
--- a/lib/chef/provider/ifconfig/redhat.rb
+++ b/lib/chef/provider/ifconfig/redhat.rb
@@ -22,6 +22,7 @@ class Chef
   class Provider
     class Ifconfig
       class Redhat < Chef::Provider::Ifconfig
+        provides :ifconfig, platform_family: %w(fedora rhel)
 
         def initialize(new_resource, run_context)
           super(new_resource, run_context)
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index 492ddda..9c7cd15 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -19,6 +19,8 @@
 #
 
 require 'chef/provider'
+require 'chef/dsl/recipe'
+require 'chef/dsl/include_recipe'
 
 class Chef
   class Provider
@@ -27,124 +29,71 @@ class Chef
     # Base class from which LWRP providers inherit.
     class LWRPBase < Provider
 
-      # Chef::Provider::LWRPBase::InlineResources
-      # Implementation of inline resource convergence for LWRP providers. See
-      # Provider::LWRPBase.use_inline_resources for a longer explanation.
-      #
-      # This code is restricted to a module so that it can be selectively
-      # applied to providers on an opt-in basis.
-      module InlineResources
-
-        # Class methods for InlineResources. Overrides the `action` DSL method
-        # with one that enables inline resource convergence.
-        module ClassMethods
-          # Defines an action method on the provider, using
-          # recipe_eval_with_update_check to execute the given block.
-          def action(name, &block)
-            define_method("action_#{name}") do
-              recipe_eval_with_update_check(&block)
-            end
-          end
-        end
-
-        # Executes the given block in a temporary run_context with its own
-        # resource collection. After the block is executed, any resources
-        # declared inside are converged, and if any are updated, the
-        # new_resource will be marked updated.
-        def recipe_eval_with_update_check(&block)
-          saved_run_context = @run_context
-          temp_run_context = @run_context.dup
-          @run_context = temp_run_context
-          @run_context.resource_collection = Chef::ResourceCollection.new
-
-          return_value = instance_eval(&block)
-          Chef::Runner.new(@run_context).converge
-          return_value
-        ensure
-          @run_context = saved_run_context
-          if temp_run_context.resource_collection.any? {|r| r.updated? }
-            new_resource.updated_by_last_action(true)
-          end
-        end
-
-      end
-
-      extend Chef::Mixin::ConvertToClassName
-      extend Chef::Mixin::FromFile
-
       include Chef::DSL::Recipe
 
       # These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
-      # They are not included by its replacment, Chef::DSL::Recipe, but
+      # They are not included by its replacement, Chef::DSL::Recipe, but
       # they may be used in existing LWRPs.
       include Chef::DSL::PlatformIntrospection
       include Chef::DSL::DataQuery
 
-      def self.build_from_file(cookbook_name, filename, run_context)
-        provider_class = nil
-        provider_name = filename_to_qualified_string(cookbook_name, filename)
+      # Allow include_recipe from within LWRP provider code
+      include Chef::DSL::IncludeRecipe
+
+      # no-op `load_current_resource`. Allows simple LWRP providers to work
+      # without defining this method explicitly (silences
+      # Chef::Exceptions::Override exception)
+      def load_current_resource
+      end
+
+      # class methods
+      class <<self
+        include Chef::Mixin::ConvertToClassName
+        include Chef::Mixin::FromFile
+
+        def build_from_file(cookbook_name, filename, run_context)
+          if LWRPBase.loaded_lwrps[filename]
+            Chef::Log.info("LWRP provider #{filename} from cookbook #{cookbook_name} has already been loaded!  Skipping the reload.")
+            return loaded_lwrps[filename]
+          end
 
-        class_name = convert_to_class_name(provider_name)
+          resource_name = filename_to_qualified_string(cookbook_name, filename)
 
-        if Chef::Provider.const_defined?(class_name, false)
-          Chef::Log.info("#{class_name} light-weight provider is already initialized -- Skipping loading #{filename}!")
-          Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.")
-          provider_class = Chef::Provider.const_get(class_name)
-        else
+          # We load the class first to give it a chance to set its own name
           provider_class = Class.new(self)
-          Chef::Provider.const_set(class_name, provider_class)
+          provider_class.provides resource_name.to_sym
           provider_class.class_from_file(filename)
-          Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}")
-        end
 
-        provider_class
-      end
+          # Respect resource_name set inside the LWRP
+          provider_class.instance_eval do
+            define_singleton_method(:to_s) do
+              "LWRP provider #{resource_name} from cookbook #{cookbook_name}"
+            end
+            define_singleton_method(:inspect) { to_s }
+          end
 
-      # Enables inline evaluation of resources in provider actions.
-      #
-      # Without this option, any resources declared inside the LWRP are added
-      # to the resource collection after the current position at the time the
-      # action is executed. Because they are added to the primary resource
-      # collection for the chef run, they can notify other resources outside
-      # the LWRP, and potentially be notified by resources outside the LWRP
-      # (but this is complicated by the fact that they don't exist until the
-      # provider executes). In this mode, it is impossible to correctly set the
-      # updated_by_last_action flag on the parent LWRP resource, since it
-      # executes and returns before its component resources are run.
-      #
-      # With this option enabled, each action creates a temporary run_context
-      # with its own resource collection, evaluates the action's code in that
-      # context, and then converges the resources created. If any resources
-      # were updated, then this provider's new_resource will be marked updated.
-      #
-      # In this mode, resources created within the LWRP cannot interact with
-      # external resources via notifies, though notifications to other
-      # resources within the LWRP will work. Delayed notifications are executed
-      # at the conclusion of the provider's action, *not* at the end of the
-      # main chef run.
-      #
-      # This mode of evaluation is experimental, but is believed to be a better
-      # set of tradeoffs than the append-after mode, so it will likely become
-      # the default in a future major release of Chef.
-      #
-      def self.use_inline_resources
-        extend InlineResources::ClassMethods
-        include InlineResources
-      end
+          Chef::Log.debug("Loaded contents of #{filename} into provider #{resource_name} (#{provider_class})")
 
-      # DSL for defining a provider's actions.
-      def self.action(name, &block)
-        define_method("action_#{name}") do
-          instance_eval(&block)
+          LWRPBase.loaded_lwrps[filename] = true
+
+          Chef::Provider.register_deprecated_lwrp_class(provider_class, convert_to_class_name(resource_name))
+
+          provider_class
         end
-      end
 
-      # no-op `load_current_resource`. Allows simple LWRP providers to work
-      # without defining this method explicitly (silences
-      # Chef::Exceptions::Override exception)
-      def load_current_resource
-      end
+        # DSL for defining a provider's actions.
+        def action(name, &block)
+          define_method("action_#{name}") do
+            instance_eval(&block)
+          end
+        end
+
+        protected
 
+        def loaded_lwrps
+          @loaded_lwrps ||= {}
+        end
+      end
     end
   end
 end
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index 1631d87..6bdfd5b 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -24,7 +24,6 @@ require 'chef/provider'
 class Chef
   class Provider
     class Mount < Chef::Provider
-
       include Chef::Mixin::ShellOut
 
       attr_accessor :unmount_retries
@@ -43,13 +42,17 @@ class Chef
       end
 
       def action_mount
-        unless current_resource.mounted
+        if current_resource.mounted
+          if mount_options_unchanged?
+            Chef::Log.debug("#{new_resource} is already mounted")
+          else
+            action_remount
+          end
+        else
           converge_by("mount #{current_resource.device} to #{current_resource.mount_point}") do
             mount_fs
             Chef::Log.info("#{new_resource} mounted")
           end
-        else
-          Chef::Log.debug("#{new_resource} is already mounted")
         end
       end
 
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 0d7e11a..510dfde 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -22,6 +22,7 @@ class Chef
   class Provider
     class Mount
       class Aix < Chef::Provider::Mount::Mount
+        provides :mount, platform: %w(aix)
 
         # Override for aix specific handling
         def initialize(new_resource, run_context)
@@ -31,7 +32,7 @@ class Chef
             @new_resource.options.clear
           end
           if @new_resource.fstype == "auto"
-            @new_resource.fstype = nil
+            @new_resource.send(:clear_fstype)
           end
         end
 
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 0a6e269..ef07416 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -24,6 +24,8 @@ class Chef
     class Mount
       class Mount < Chef::Provider::Mount
 
+        provides :mount
+
         def initialize(new_resource, run_context)
           super
           @real_device = nil
diff --git a/lib/chef/provider/mount/solaris.rb b/lib/chef/provider/mount/solaris.rb
index d8cec24..deb04d4 100644
--- a/lib/chef/provider/mount/solaris.rb
+++ b/lib/chef/provider/mount/solaris.rb
@@ -27,6 +27,8 @@ class Chef
     class Mount
       # Mount Solaris File systems
       class Solaris < Chef::Provider::Mount
+        provides :mount, platform: %w(openindiana opensolaris nexentacore omnios solaris2 smartos)
+
         extend Forwardable
 
         VFSTAB = '/etc/vfstab'.freeze
diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb
index a6b5ab5..b7f4aa7 100644
--- a/lib/chef/provider/ohai.rb
+++ b/lib/chef/provider/ohai.rb
@@ -21,6 +21,7 @@ require 'ohai'
 class Chef
   class Provider
     class Ohai < Chef::Provider
+      provides :ohai
 
       def whyrun_supported?
         true
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index 2e8e299..8e98a10 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,6 +18,7 @@
 
 require 'chef/mixin/shell_out'
 require 'chef/mixin/command'
+require 'chef/mixin/subclass_directive'
 require 'chef/log'
 require 'chef/file_cache'
 require 'chef/platform'
@@ -27,6 +28,12 @@ class Chef
     class Package < Chef::Provider
       include Chef::Mixin::Command
       include Chef::Mixin::ShellOut
+      extend Chef::Mixin::SubclassDirective
+
+      # subclasses declare this if they want all their arguments as arrays of packages and names
+      subclass_directive :use_multipackage_api
+      # subclasses declare this if they want sources (filenames) pulled from their package names
+      subclass_directive :use_package_name_for_source
 
       #
       # Hook that subclasses use to populate the candidate_version(s)
@@ -43,6 +50,14 @@ class Chef
         true
       end
 
+      def check_resource_semantics!
+        # FIXME: this is not universally true and subclasses are needing to override this and no-ops it.  It should be turned into
+        # another "subclass_directive" and the apt and yum providers should declare that they need this behavior.
+        if new_resource.package_name.is_a?(Array) && new_resource.source != nil
+          raise Chef::Exceptions::InvalidResourceSpecification, "You may not specify both multipackage and source"
+        end
+      end
+
       def load_current_resource
       end
 
@@ -80,11 +95,10 @@ class Chef
           end
         end
 
-        # XXX: mutating the new resource is generally bad
-        @new_resource.version(versions_for_new_resource)
-
         converge_by(install_description) do
-          install_package(package_names_for_targets, versions_for_targets)
+          multipackage_api_adapter(package_names_for_targets, versions_for_targets) do |name, version|
+            install_package(name, version)
+          end
           Chef::Log.info("#{@new_resource} installed #{package_names_for_targets} at #{versions_for_targets}")
         end
       end
@@ -107,11 +121,10 @@ class Chef
           return
         end
 
-        # XXX: mutating the new resource is generally bad
-        @new_resource.version(versions_for_new_resource)
-
         converge_by(upgrade_description) do
-          upgrade_package(package_names_for_targets, versions_for_targets)
+          multipackage_api_adapter(package_names_for_targets, versions_for_targets) do |name, version|
+            upgrade_package(name, version)
+          end
           log_allow_downgrade = allow_downgrade ? '(allow_downgrade)' : ''
           Chef::Log.info("#{@new_resource} upgraded#{log_allow_downgrade} #{package_names_for_targets} to #{versions_for_targets}")
         end
@@ -132,12 +145,13 @@ class Chef
 
       private :upgrade_description
 
-      # @todo: ability to remove an array of packages
       def action_remove
         if removing_package?
           description = @new_resource.version ? "version #{@new_resource.version} of " :  ""
-          converge_by("remove #{description} package #{@current_resource.package_name}") do
-            remove_package(@current_resource.package_name, @new_resource.version)
+          converge_by("remove #{description}package #{@current_resource.package_name}") do
+            multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version|
+              remove_package(name, version)
+            end
             Chef::Log.info("#{@new_resource} removed")
           end
         else
@@ -166,18 +180,18 @@ class Chef
         end
       end
 
-      # @todo: ability to purge an array of packages
       def action_purge
         if removing_package?
           description = @new_resource.version ? "version #{@new_resource.version} of" : ""
           converge_by("purge #{description} package #{@current_resource.package_name}") do
-            purge_package(@current_resource.package_name, @new_resource.version)
+            multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version|
+              purge_package(name, version)
+            end
             Chef::Log.info("#{@new_resource} purged")
           end
         end
       end
 
-      # @todo: ability to reconfigure an array of packages
       def action_reconfig
         if @current_resource.version == nil then
           Chef::Log.debug("#{@new_resource} is NOT installed - nothing to do")
@@ -192,7 +206,10 @@ class Chef
         if preseed_file = get_preseed_file(@new_resource.package_name, @current_resource.version)
           converge_by("reconfigure package #{@new_resource.package_name}") do
             preseed_package(preseed_file)
-            reconfig_package(@new_resource.package_name, @current_resource.version)
+            multipackage_api_adapter(@new_resource.package_name, @current_resource.version) do |name, version|
+              reconfig_package(name, version)
+
+            end
             Chef::Log.info("#{@new_resource} reconfigured")
           end
         else
@@ -201,6 +218,15 @@ class Chef
       end
 
       # @todo use composition rather than inheritance
+
+      def multipackage_api_adapter(name, version)
+        if use_multipackage_api?
+          yield [name].flatten, [version].flatten
+        else
+          yield name, version
+        end
+      end
+
       def install_package(name, version)
         raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :install"
       end
@@ -225,7 +251,7 @@ class Chef
         raise( Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reconfig" )
       end
 
-      # this is heavily used by subclasses
+      # used by subclasses.  deprecated.  use #a_to_s instead.
       def expand_options(options)
         options ? " #{options}" : ""
       end
@@ -316,18 +342,6 @@ class Chef
         multipackage? ? versions_for_targets : versions_for_targets[0]
       end
 
-      # We need to mutate @new_resource.version() for some reason and this is a helper so that we inject the right
-      # class (String or Array) into that attribute based on if we're handling an array of package names or not.
-      #
-      # @return [String, Array<String>] target_versions coerced into the correct type for back-compat
-      def versions_for_new_resource
-        if multipackage?
-          target_version_array
-        else
-          target_version_array[0]
-        end
-      end
-
       # Return an array indexed the same as *_version_array which contains either the target version to install/upgrade to
       # or else nil if the package is not being modified.
       #
@@ -464,10 +478,38 @@ class Chef
 
       # @return [Array] new_version(s) as an array
       def new_version_array
-        @new_version_array ||=
-            [ new_resource.version ].flatten.map do |v|
-              ( v.nil? || v.empty? ) ? nil : v
+        [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v }
+      end
+
+      # TIP: less error prone to simply always call resolved_source_array, even if you
+      # don't think that you need to.
+      #
+      # @return [Array] new_resource.source as an array
+      def source_array
+        if new_resource.source.nil?
+          package_name_array.map { nil }
+        else
+          [ new_resource.source ].flatten
+        end
+      end
+
+      # Helper to handle use_package_name_for_source to convert names into local packages to install.
+      #
+      # @return [Array] Array of sources with package_names converted to sources
+      def resolved_source_array
+        @resolved_source_array ||=
+          begin
+            source_array.each_with_index.map do |source, i|
+              package_name = package_name_array[i]
+              # we require at least one '/' in the package_name to avoid [XXX_]package 'foo' breaking due to a random 'foo' file in cwd
+              if use_package_name_for_source? && source.nil? && package_name.match(/#{::File::SEPARATOR}/) && ::File.exist?(package_name)
+                Chef::Log.debug("No package source specified, but #{package_name} exists on filesystem, using #{package_name} as source.")
+                package_name
+              else
+                source
+              end
             end
+          end
       end
 
       # @todo: extract apt/dpkg specific preseeding to a helper class
@@ -487,6 +529,37 @@ class Chef
           false
         end
       end
+
+      def shell_out_with_timeout(*command_args)
+        shell_out(*add_timeout_option(command_args))
+      end
+
+      def shell_out_with_timeout!(*command_args)
+        shell_out!(*add_timeout_option(command_args))
+      end
+
+      def add_timeout_option(command_args)
+        args = command_args.dup
+        if args.last.is_a?(Hash)
+          options = args.pop.dup
+          options[:timeout] = new_resource.timeout if new_resource.timeout
+          options[:timeout] = 900 unless options.has_key?(:timeout)
+          args << options
+        else
+          args << { :timeout => new_resource.timeout ? new_resource.timeout : 900 }
+        end
+        args
+      end
+
+      # Helper for sublcasses to convert an array of string args into a string.  It
+      # will compact nil or empty strings in the array and will join the array elements
+      # with spaces, without introducing any double spaces for nil/empty elements.
+      #
+      # @param args [String] variable number of string arguments
+      # @return [String] nicely concatenated string or empty string
+      def a_to_s(*args)
+        args.reject {|i| i.nil? || i == "" }.join(" ")
+      end
     end
   end
 end
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
index 107f914..5165f4b 100644
--- a/lib/chef/provider/package/aix.rb
+++ b/lib/chef/provider/package/aix.rb
@@ -26,6 +26,7 @@ class Chef
     class Package
       class Aix < Chef::Provider::Package
 
+        provides :package, os: "aix"
         provides :bff_package, os: "aix"
 
         include Chef::Mixin::GetSourceFromPackage
@@ -52,7 +53,7 @@ class Chef
             @package_source_found = ::File.exists?(@new_resource.source)
             if @package_source_found
               Chef::Log.debug("#{@new_resource} checking pkg status")
-              ret = shell_out("installp -L -d #{@new_resource.source}")
+              ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}")
               ret.stdout.each_line do | line |
                 case line
                 when /#{@new_resource.package_name}:/
@@ -60,11 +61,12 @@ class Chef
                   @new_resource.version(fields[2])
                 end
               end
+              raise Chef::Exceptions::Package, "package source #{@new_resource.source} does not provide package #{@new_resource.package_name}" unless @new_resource.version
             end
           end
 
           Chef::Log.debug("#{@new_resource} checking install state")
-          ret = shell_out("lslpp -lcq #{@current_resource.package_name}")
+          ret = shell_out_with_timeout("lslpp -lcq #{@current_resource.package_name}")
           ret.stdout.each_line do | line |
             case line
             when /#{@current_resource.package_name}/
@@ -83,7 +85,7 @@ class Chef
 
         def candidate_version
           return @candidate_version if @candidate_version
-          ret = shell_out("installp -L -d #{@new_resource.source}")
+          ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}")
           ret.stdout.each_line do | line |
             case line
             when /\w:#{Regexp.escape(@new_resource.package_name)}:(.*)/
@@ -109,10 +111,10 @@ class Chef
         def install_package(name, version)
           Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
           if @new_resource.options.nil?
-            shell_out!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" )
+            shell_out_with_timeout!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" )
             Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
           else
-            shell_out!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" )
+            shell_out_with_timeout!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" )
             Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
           end
         end
@@ -121,10 +123,10 @@ class Chef
 
         def remove_package(name, version)
           if @new_resource.options.nil?
-            shell_out!( "installp -u #{name}" )
+            shell_out_with_timeout!( "installp -u #{name}" )
             Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
           else
-            shell_out!( "installp -u #{expand_options(@new_resource.options)} #{name}" )
+            shell_out_with_timeout!( "installp -u #{expand_options(@new_resource.options)} #{name}" )
             Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
           end
         end
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index e426b51..e109c99 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -25,6 +25,7 @@ class Chef
     class Package
       class Apt < Chef::Provider::Package
 
+        provides :package, platform_family: "debian"
         provides :apt_package, os: "linux"
 
         # return [Hash] mapping of package name to Boolean value
@@ -62,7 +63,7 @@ class Chef
           installed_version  = nil
           candidate_version  = nil
 
-          shell_out!("apt-cache#{expand_options(default_release_options)} policy #{pkg}", {:timeout=>900}).stdout.each_line do |line|
+          shell_out_with_timeout!("apt-cache#{expand_options(default_release_options)} policy #{pkg}").stdout.each_line do |line|
             case line
             when /^\s{2}Installed: (.+)$/
               installed_version = $1
@@ -78,7 +79,7 @@ class Chef
               if candidate_version == '(none)'
                 # This may not be an appropriate assumption, but it shouldn't break anything that already worked -- btm
                 is_virtual_package = true
-                showpkg = shell_out!("apt-cache showpkg #{pkg}", {:timeout => 900}).stdout
+                showpkg = shell_out_with_timeout!("apt-cache showpkg #{pkg}").stdout
                 providers = Hash.new
                 showpkg.rpartition(/Reverse Provides: ?#{$/}/)[2].each_line do |line|
                   provider, version = line.split
@@ -175,7 +176,7 @@ class Chef
         # interactive prompts. Command is run with default localization rather
         # than forcing locale to "C", so command output may not be stable.
         def run_noninteractive(command)
-          shell_out!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil }, :timeout => @new_resource.timeout)
+          shell_out_with_timeout!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
         end
 
       end
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index 11691a2..0da675e 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -17,130 +17,207 @@
 #
 
 require 'chef/provider/package'
-require 'chef/mixin/command'
 require 'chef/resource/package'
-require 'chef/mixin/get_source_from_package'
 
 class Chef
   class Provider
     class Package
       class Dpkg < Chef::Provider::Package
-        # http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
-        DPKG_INFO = /([a-z\d\-\+\.]+)\t([\w\d.~:-]+)/
+        DPKG_REMOVED = /^Status: deinstall ok config-files/
         DPKG_INSTALLED = /^Status: install ok installed/
         DPKG_VERSION = /^Version: (.+)$/
 
         provides :dpkg_package, os: "linux"
 
-        include Chef::Mixin::GetSourceFromPackage
+        use_multipackage_api
+        use_package_name_for_source
 
         def define_resource_requirements
           super
-          requirements.assert(:install) do |a|
-            a.assertion{ not @new_resource.source.nil? }
-            a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+
+          requirements.assert(:install, :upgrade) do |a|
+            a.assertion { !resolved_source_array.compact.empty? }
+            a.failure_message Chef::Exceptions::Package, "#{new_resource} the source property is required for action :install or :upgrade"
           end
 
-          # TODO this was originally written for any action in which .source is provided
-          # but would it make more sense to only look at source if the action is :install?
-          requirements.assert(:all_actions) do |a|
-            a.assertion { @source_exists }
-            a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
-            a.whyrun "Assuming it would have been previously downloaded."
+          requirements.assert(:install, :upgrade) do |a|
+            a.assertion { source_files_exist? }
+            a.failure_message Chef::Exceptions::Package, "#{new_resource} source file(s) do not exist: #{missing_sources}"
+            a.whyrun "Assuming they would have been previously created."
           end
         end
 
         def load_current_resource
-          @source_exists = true
-          @current_resource = Chef::Resource::Package.new(@new_resource.name)
-          @current_resource.package_name(@new_resource.package_name)
-          @new_resource.version(nil)
-
-          if @new_resource.source
-            @source_exists = ::File.exists?(@new_resource.source)
-            if @source_exists
-              # Get information from the package if supplied
-              Chef::Log.debug("#{@new_resource} checking dpkg status")
-
-              shell_out("dpkg-deb -W #{@new_resource.source}").stdout.each_line do |line|
-                if pkginfo = DPKG_INFO.match(line)
-                  @current_resource.package_name(pkginfo[1])
-                  @new_resource.version(pkginfo[2])
-                  @candidate_version = pkginfo[2]
-                end
-              end
-            else
-              # Source provided but not valid means we can't safely do further processing
-              return
-            end
-
+          @current_resource = Chef::Resource::Package.new(new_resource.name)
+          current_resource.package_name(new_resource.package_name)
+
+          if source_files_exist?
+            @candidate_version = get_candidate_version
+            current_resource.package_name(get_package_name)
+            # if the source file exists then our package_name is right
+            current_resource.version(get_current_version_from(current_package_name_array))
+          elsif !installing?
+            # we can't do this if we're installing with no source, because our package_name
+            # is probably not right.
+            #
+            # if we're removing or purging we don't use source, and our package_name must
+            # be right so we can do this.
+            #
+            # we don't error here on the dpkg command since we'll handle the exception or
+            # the why-run message in define_resource_requirements.
+            current_resource.version(get_current_version_from(current_package_name_array))
           end
 
-          # Check to see if it is installed
-          package_installed = nil
-          Chef::Log.debug("#{@new_resource} checking install state")
-          status = shell_out("dpkg -s #{@current_resource.package_name}")
+          current_resource
+        end
+
+        def install_package(name, version)
+          sources = name.map { |n| name_sources[n] }
+          Chef::Log.info("#{new_resource} installing package(s): #{name.join(' ')}")
+          run_noninteractive("dpkg -i", new_resource.options, *sources)
+        end
+
+        def remove_package(name, version)
+          Chef::Log.info("#{new_resource} removing package(s): #{name.join(' ')}")
+          run_noninteractive("dpkg -r", new_resource.options, *name)
+        end
+
+        def purge_package(name, version)
+          Chef::Log.info("#{new_resource} purging packages(s): #{name.join(' ')}")
+          run_noninteractive("dpkg -P", new_resource.options, *name)
+        end
+
+        def upgrade_package(name, version)
+          install_package(name, version)
+        end
+
+        def preseed_package(preseed_file)
+          Chef::Log.info("#{new_resource} pre-seeding package installation instructions")
+          run_noninteractive("debconf-set-selections", *preseed_file)
+        end
+
+        def reconfig_package(name, version)
+          Chef::Log.info("#{new_resource} reconfiguring")
+          run_noninteractive("dpkg-reconfigure", *name)
+        end
+
+        # Override the superclass check.  Multiple sources are required here.
+        def check_resource_semantics!
+        end
+
+        private
+
+        def read_current_version_of_package(package_name)
+          Chef::Log.debug("#{new_resource} checking install state of #{package_name}")
+          status = shell_out_with_timeout!("dpkg -s #{package_name}", returns: [0, 1])
+          package_installed = false
           status.stdout.each_line do |line|
             case line
+            when DPKG_REMOVED
+              # if we are 'purging' then we consider 'removed' to be 'installed'
+              package_installed = true if action == :purge
             when DPKG_INSTALLED
               package_installed = true
             when DPKG_VERSION
               if package_installed
-                Chef::Log.debug("#{@new_resource} current version is #{$1}")
-                @current_resource.version($1)
+                Chef::Log.debug("#{new_resource} current version is #{$1}")
+                return $1
               end
             end
           end
+          return nil
+        end
 
-          unless status.exitstatus == 0 || status.exitstatus == 1
-            raise Chef::Exceptions::Package, "dpkg failed - #{status.inspect}!"
+        def get_current_version_from(array)
+          array.map do |name|
+            read_current_version_of_package(name)
           end
+        end
 
-          @current_resource
+        # Runs command via shell_out_with_timeout with magic environment to disable
+        # interactive prompts.
+        def run_noninteractive(*command)
+          shell_out_with_timeout!(a_to_s(*command), :env => { "DEBIAN_FRONTEND" => "noninteractive" })
         end
 
-        def install_package(name, version)
-          Chef::Log.info("#{@new_resource} installing #{@new_resource.source}")
-          run_noninteractive(
-            "dpkg -i#{expand_options(@new_resource.options)} #{@new_resource.source}"
-          )
+        # Returns true if all sources exist.  Returns false if any do not, or if no
+        # sources were specified.
+        #
+        # @return [Boolean] True if all sources exist
+        def source_files_exist?
+          resolved_source_array.all? {|s| s && ::File.exist?(s) }
         end
 
-        def remove_package(name, version)
-          Chef::Log.info("#{@new_resource} removing #{@new_resource.package_name}")
-          run_noninteractive(
-            "dpkg -r#{expand_options(@new_resource.options)} #{@new_resource.package_name}"
-          )
+        # Helper to return all the nanes of the missing sources for error messages.
+        #
+        # @return [Array<String>] Array of missing sources
+        def missing_sources
+          resolved_source_array.select {|s| s.nil? || !::File.exist?(s) }
         end
 
-        def purge_package(name, version)
-          Chef::Log.info("#{@new_resource} purging #{@new_resource.package_name}")
-          run_noninteractive(
-            "dpkg -P#{expand_options(@new_resource.options)} #{@new_resource.package_name}"
-          )
+        def current_package_name_array
+          [ current_resource.package_name ].flatten
         end
 
-        def upgrade_package(name, version)
-          install_package(name, version)
+        # Helper to construct Hash of names-to-sources.
+        #
+        # @return [Hash] Mapping of package names to sources
+        def name_sources
+          @name_sources =
+            begin
+              Hash[*package_name_array.zip(resolved_source_array).flatten]
+            end
         end
 
-        def preseed_package(preseed_file)
-          Chef::Log.info("#{@new_resource} pre-seeding package installation instructions")
-          run_noninteractive("debconf-set-selections #{preseed_file}")
+        # Helper to construct Hash of names-to-package-information.
+        #
+        # @return [Hash] Mapping of package names to package information
+        def name_pkginfo
+          @name_pkginfo ||=
+            begin
+              pkginfos = resolved_source_array.map do |src|
+                Chef::Log.debug("#{new_resource} checking #{src} dpkg status")
+                status = shell_out_with_timeout!("dpkg-deb -W #{src}")
+                status.stdout
+              end
+              Hash[*package_name_array.zip(pkginfos).flatten]
+            end
         end
 
-        def reconfig_package(name, version)
-          Chef::Log.info("#{@new_resource} reconfiguring")
-          run_noninteractive("dpkg-reconfigure #{name}")
+        def name_candidate_version
+          @name_candidate_version ||=
+            begin
+              Hash[name_pkginfo.map {|k, v| [k, v ? v.split("\t")[1].strip : nil] }]
+            end
+        end
+
+        def name_package_name
+          @name_package_name ||=
+            begin
+              Hash[name_pkginfo.map {|k, v| [k, v ? v.split("\t")[0] : nil] }]
+            end
+        end
+
+        # Return candidate version array from pkg-deb -W against the source file(s).
+        #
+        # @return [Array] Array of candidate versions read from the source files
+        def get_candidate_version
+          package_name_array.map { |name| name_candidate_version[name] }
+        end
+
+        # Return package names from the candidate source file(s).
+        #
+        # @return [Array] Array of actual package names read from the source files
+        def get_package_name
+          package_name_array.map { |name| name_package_name[name] }
         end
 
-        # Runs command via shell_out with magic environment to disable
-        # interactive prompts. Command is run with default localization rather
-        # than forcing locale to "C", so command output may not be stable.
+        # Since upgrade just calls install, this is a helper to determine
+        # if our action means that we'll be calling install_package.
         #
-        # FIXME: This should be "LC_ALL" => "en_US.UTF-8" in order to stabilize the output and get UTF-8
-        def run_noninteractive(command)
-          shell_out!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+        # @return [Boolean] true if we're doing :install or :upgrade
+        def installing?
+          [:install, :upgrade].include?(action)
         end
 
       end
diff --git a/lib/chef/provider/package/easy_install.rb b/lib/chef/provider/package/easy_install.rb
index 90727b7..2f7880b 100644
--- a/lib/chef/provider/package/easy_install.rb
+++ b/lib/chef/provider/package/easy_install.rb
@@ -32,10 +32,10 @@ class Chef
 
           begin
             # first check to see if we can import it
-            output = shell_out!("#{python_binary_path} -c \"import #{name}\"", :returns=>[0,1]).stderr
+            output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{name}\"", :returns=>[0,1]).stderr
             if output.include? "ImportError"
               # then check to see if its on the path
-              output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+              output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
               if output.downcase.include? "#{name.downcase}"
                 check = true
               end
@@ -73,10 +73,10 @@ class Chef
           package_version = nil
           if install_check(module_name)
             begin
-              output = shell_out!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout
+              output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout
               package_version = output.strip
             rescue
-              output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+              output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
 
               output_array = output.gsub(/[\[\]]/,'').split(/\s*,\s*/)
               package_path = ""
@@ -107,7 +107,7 @@ class Chef
            return @candidate_version if @candidate_version
 
            # do a dry run to get the latest version
-           result = shell_out!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns=>[0,1])
+           result = shell_out_with_timeout!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns=>[0,1])
            @candidate_version = result.stdout[/(.*)Best match: (.*) (.*)$/, 3]
            @candidate_version
         end
diff --git a/lib/chef/provider/package/freebsd/base.rb b/lib/chef/provider/package/freebsd/base.rb
index 6a3b97a..7c032b3 100644
--- a/lib/chef/provider/package/freebsd/base.rb
+++ b/lib/chef/provider/package/freebsd/base.rb
@@ -47,7 +47,7 @@ class Chef
 
             # Otherwise look up the path to the ports directory using 'whereis'
             else
-              whereis = shell_out!("whereis -s #{port}", :env => nil)
+              whereis = shell_out_with_timeout!("whereis -s #{port}", :env => nil)
               unless path = whereis.stdout[/^#{Regexp.escape(port)}:\s+(.+)$/, 1]
                 raise Chef::Exceptions::Package, "Could not find port with the name #{port}"
               end
@@ -57,7 +57,7 @@ class Chef
 
           def makefile_variable_value(variable, dir = nil)
             options = dir ? { :cwd => dir } : {}
-            make_v = shell_out!("make -V #{variable}", options.merge!(:env => nil, :returns => [0,1]))
+            make_v = shell_out_with_timeout!("make -V #{variable}", options.merge!(:env => nil, :returns => [0,1]))
             make_v.exitstatus.zero? ? make_v.stdout.strip.split($\).first : nil   # $\ is the line separator, i.e. newline.
           end
         end
diff --git a/lib/chef/provider/package/freebsd/pkg.rb b/lib/chef/provider/package/freebsd/pkg.rb
index ebbfbb1..33a8c2c 100644
--- a/lib/chef/provider/package/freebsd/pkg.rb
+++ b/lib/chef/provider/package/freebsd/pkg.rb
@@ -34,24 +34,24 @@ class Chef
               case @new_resource.source
               when /^http/, /^ftp/
                 if @new_resource.source =~ /\/$/
-                  shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, 'LC_ALL' => nil }).status
+                  shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, 'LC_ALL' => nil }).status
                 else
-                  shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, 'LC_ALL' => nil }).status
+                  shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, 'LC_ALL' => nil }).status
                 end
                 Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
 
               when /^\//
-                shell_out!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , 'LC_ALL'=>nil}).status
+                shell_out_with_timeout!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , 'LC_ALL'=>nil}).status
                 Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
 
               else
-                shell_out!("pkg_add -r #{latest_link_name}", :env => nil).status
+                shell_out_with_timeout!("pkg_add -r #{latest_link_name}", :env => nil).status
               end
             end
           end
 
           def remove_package(name, version)
-            shell_out!("pkg_delete #{package_name}-#{version || @current_resource.version}", :env => nil).status
+            shell_out_with_timeout!("pkg_delete #{package_name}-#{version || @current_resource.version}", :env => nil).status
           end
 
           # The name of the package (without the version number) as understood by pkg_add and pkg_info.
@@ -72,7 +72,7 @@ class Chef
           end
 
           def current_installed_version
-            pkg_info = shell_out!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
+            pkg_info = shell_out_with_timeout!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
             pkg_info.stdout[/^#{Regexp.escape(package_name)}-(.+)/, 1]
           end
 
diff --git a/lib/chef/provider/package/freebsd/pkgng.rb b/lib/chef/provider/package/freebsd/pkgng.rb
index bfe6dca..2fdc9dd 100644
--- a/lib/chef/provider/package/freebsd/pkgng.rb
+++ b/lib/chef/provider/package/freebsd/pkgng.rb
@@ -28,11 +28,11 @@ class Chef
             unless @current_resource.version
               case @new_resource.source
               when /^(http|ftp|\/)/
-                shell_out!("pkg add#{expand_options(@new_resource.options)} #{@new_resource.source}", :env => { 'LC_ALL' => nil }).status
+                shell_out_with_timeout!("pkg add#{expand_options(@new_resource.options)} #{@new_resource.source}", :env => { 'LC_ALL' => nil }).status
                 Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
 
               else
-                shell_out!("pkg install -y#{expand_options(@new_resource.options)} #{name}", :env => { 'LC_ALL' => nil }).status
+                shell_out_with_timeout!("pkg install -y#{expand_options(@new_resource.options)} #{name}", :env => { 'LC_ALL' => nil }).status
               end
             end
           end
@@ -40,11 +40,11 @@ class Chef
           def remove_package(name, version)
             options = @new_resource.options && @new_resource.options.sub(repo_regex, '')
             options && !options.empty? || options = nil
-            shell_out!("pkg delete -y#{expand_options(options)} #{name}#{version ? '-' + version : ''}", :env => nil).status
+            shell_out_with_timeout!("pkg delete -y#{expand_options(options)} #{name}#{version ? '-' + version : ''}", :env => nil).status
           end
 
           def current_installed_version
-            pkg_info = shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
+            pkg_info = shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
             pkg_info.stdout[/^Version +: (.+)$/, 1]
           end
 
@@ -63,7 +63,7 @@ class Chef
               options = $1
             end
 
-            pkg_query = shell_out!("pkg rquery#{expand_options(options)} '%v' #{@new_resource.package_name}", :env => nil)
+            pkg_query = shell_out_with_timeout!("pkg rquery#{expand_options(options)} '%v' #{@new_resource.package_name}", :env => nil)
             pkg_query.exitstatus.zero? ? pkg_query.stdout.strip.split(/\n/).last : nil
           end
 
diff --git a/lib/chef/provider/package/freebsd/port.rb b/lib/chef/provider/package/freebsd/port.rb
index 8b19117..3fbd002 100644
--- a/lib/chef/provider/package/freebsd/port.rb
+++ b/lib/chef/provider/package/freebsd/port.rb
@@ -26,18 +26,18 @@ class Chef
           include PortsHelper
 
           def install_package(name, version)
-            shell_out!("make -DBATCH install clean", :timeout => 1800, :env => nil, :cwd => port_dir).status
+            shell_out_with_timeout!("make -DBATCH install clean", :timeout => 1800, :env => nil, :cwd => port_dir).status
           end
 
           def remove_package(name, version)
-            shell_out!("make deinstall", :timeout => 300, :env => nil, :cwd => port_dir).status
+            shell_out_with_timeout!("make deinstall", :timeout => 300, :env => nil, :cwd => port_dir).status
           end
 
           def current_installed_version
             pkg_info = if @new_resource.supports_pkgng?
-                         shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
+                         shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
                        else
-                         shell_out!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0,1])
+                         shell_out_with_timeout!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0,1])
                        end
             pkg_info.stdout[/^#{Regexp.escape(@new_resource.package_name)}-(.+)/, 1]
           end
diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb
index 6038996..e5c45f0 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -26,8 +26,8 @@ class Chef
     class Package
       class Homebrew < Chef::Provider::Package
 
+        provides :package, os: "darwin", override: true
         provides :homebrew_package
-        provides :package, os: "darwin"
 
         include Chef::Mixin::HomebrewUser
 
@@ -126,7 +126,8 @@ class Chef
           homebrew_user = Etc.getpwuid(homebrew_uid)
 
           Chef::Log.debug "Executing '#{command}' as user '#{homebrew_user.name}'"
-          output = shell_out!(command, :timeout => 1800, :user => homebrew_uid, :environment => { 'HOME' => homebrew_user.dir, 'RUBYOPT' => nil })
+          # FIXME: this 1800 second default timeout should be deprecated
+          output = shell_out_with_timeout!(command, :timeout => 1800, :user => homebrew_uid, :environment => { 'HOME' => homebrew_user.dir, 'RUBYOPT' => nil })
           output.stdout.chomp
         end
 
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
index 87022d7..96c2e71 100644
--- a/lib/chef/provider/package/ips.rb
+++ b/lib/chef/provider/package/ips.rb
@@ -27,6 +27,7 @@ class Chef
     class Package
       class Ips < Chef::Provider::Package
 
+        provides :package, platform: %w(openindiana opensolaris omnios solaris2)
         provides :ips_package, os: "solaris2"
 
         attr_accessor :virtual
@@ -42,14 +43,14 @@ class Chef
         end
 
         def get_current_version
-          shell_out("pkg info #{@new_resource.package_name}").stdout.each_line do |line|
+          shell_out_with_timeout("pkg info #{@new_resource.package_name}").stdout.each_line do |line|
             return $1.split[0] if line =~ /^\s+Version: (.*)/
           end
           return nil
         end
 
         def get_candidate_version
-          shell_out!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line|
+          shell_out_with_timeout!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line|
             return $1.split[0] if line =~ /Version: (.*)/
           end
           return nil
@@ -73,7 +74,7 @@ class Chef
             else
               normal_command
             end
-          shell_out(command)
+          shell_out_with_timeout(command)
         end
 
         def upgrade_package(name, version)
@@ -82,7 +83,7 @@ class Chef
 
         def remove_package(name, version)
           package_name = "#{name}@#{version}"
-          shell_out!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" )
+          shell_out_with_timeout!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" )
         end
       end
     end
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
index b252344..c7ea71a 100644
--- a/lib/chef/provider/package/macports.rb
+++ b/lib/chef/provider/package/macports.rb
@@ -3,8 +3,8 @@ class Chef
     class Package
       class Macports < Chef::Provider::Package
 
-        provides :macports_package
         provides :package, os: "darwin"
+        provides :macports_package
 
         def load_current_resource
           @current_resource = Chef::Resource::Package.new(@new_resource.name)
@@ -49,21 +49,21 @@ class Chef
           unless @current_resource.version == version
             command = "port#{expand_options(@new_resource.options)} install #{name}"
             command << " @#{version}" if version and !version.empty?
-            shell_out!(command)
+            shell_out_with_timeout!(command)
           end
         end
 
         def purge_package(name, version)
           command = "port#{expand_options(@new_resource.options)} uninstall #{name}"
           command << " @#{version}" if version and !version.empty?
-          shell_out!(command)
+          shell_out_with_timeout!(command)
         end
 
         def remove_package(name, version)
           command = "port#{expand_options(@new_resource.options)} deactivate #{name}"
           command << " @#{version}" if version and !version.empty?
 
-          shell_out!(command)
+          shell_out_with_timeout!(command)
         end
 
         def upgrade_package(name, version)
@@ -76,14 +76,14 @@ class Chef
             # that hasn't been installed.
             install_package(name, version)
           elsif current_version != version
-            shell_out!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" )
+            shell_out_with_timeout!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" )
           end
         end
 
         private
         def get_response_from_command(command)
           output = nil
-          status = shell_out(command)
+          status = shell_out_with_timeout(command)
           begin
             output = status.stdout
           rescue Exception
diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb
index 82048c3..00d9e59 100644
--- a/lib/chef/provider/package/openbsd.rb
+++ b/lib/chef/provider/package/openbsd.rb
@@ -22,7 +22,6 @@
 
 require 'chef/resource/package'
 require 'chef/provider/package'
-require 'chef/mixin/shell_out'
 require 'chef/mixin/get_source_from_package'
 require 'chef/exceptions'
 
@@ -32,6 +31,7 @@ class Chef
       class Openbsd < Chef::Provider::Package
 
         provides :package, os: "openbsd"
+        provides :openbsd_package
 
         include Chef::Mixin::ShellOut
         include Chef::Mixin::GetSourceFromPackage
@@ -72,18 +72,16 @@ class Chef
             if parts = name.match(/^(.+?)--(.+)/) # use double-dash for stems with flavors, see man page for pkg_add
               name = parts[1]
             end
-            shell_out!("pkg_add -r #{name}#{version_string}", :env => {"PKG_PATH" => pkg_path}).status
+            shell_out_with_timeout!("pkg_add -r #{name}#{version_string(version)}", :env => {"PKG_PATH" => pkg_path}).status
             Chef::Log.debug("#{new_resource.package_name} installed")
           end
         end
 
         def remove_package(name, version)
-          version_string  = ''
-          version_string += "-#{version}" if version
           if parts = name.match(/^(.+?)--(.+)/)
             name = parts[1]
           end
-          shell_out!("pkg_delete #{name}#{version_string}", :env => nil).status
+          shell_out_with_timeout!("pkg_delete #{name}#{version_string(version)}", :env => nil).status
         end
 
         private
@@ -94,7 +92,7 @@ class Chef
           else
             name = new_resource.package_name
           end
-          pkg_info = shell_out!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0,1])
+          pkg_info = shell_out_with_timeout!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0,1])
           result = pkg_info.stdout[/^inst:#{Regexp.escape(name)}-(.+?)\s/, 1]
           Chef::Log.debug("installed_version of '#{new_resource.package_name}' is '#{result}'")
           result
@@ -103,7 +101,7 @@ class Chef
         def candidate_version
           @candidate_version ||= begin
             results = []
-            shell_out!("pkg_info -I \"#{new_resource.package_name}#{version_string}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line|
+            shell_out_with_timeout!("pkg_info -I \"#{new_resource.package_name}#{version_string(new_resource.version)}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line|
               if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
                 results << line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1]
               else
@@ -111,7 +109,7 @@ class Chef
               end
             end
             results = results.reject(&:nil?)
-            Chef::Log.debug("candidate versions of '#{new_resource.package_name}' are '#{results}'")
+            Chef::Log.debug("Candidate versions of '#{new_resource.package_name}' are '#{results}'")
             case results.length
             when 0
               []
@@ -123,9 +121,9 @@ class Chef
           end
         end
 
-        def version_string
+        def version_string(version)
           ver  = ''
-          ver += "-#{new_resource.version}" if new_resource.version
+          ver += "-#{version}" if version
         end
 
         def pkg_path
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index f16fc81..01e3a9c 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -25,6 +25,7 @@ class Chef
     class Package
       class Pacman < Chef::Provider::Package
 
+        provides :package, platform: "arch"
         provides :pacman_package, os: "linux"
 
         def load_current_resource
@@ -34,7 +35,7 @@ class Chef
           @current_resource.version(nil)
 
           Chef::Log.debug("#{@new_resource} checking pacman for #{@new_resource.package_name}")
-          status = shell_out("pacman -Qi #{@new_resource.package_name}")
+          status = shell_out_with_timeout("pacman -Qi #{@new_resource.package_name}")
           status.stdout.each_line do |line|
             case line
             when /^Version(\s?)*: (.+)$/
@@ -62,7 +63,7 @@ class Chef
 
           package_repos = repos.map {|r| Regexp.escape(r) }.join('|')
 
-          status = shell_out("pacman -Sl")
+          status = shell_out_with_timeout("pacman -Sl")
           status.stdout.each_line do |line|
             case line
               when /^(#{package_repos}) #{Regexp.escape(@new_resource.package_name)} (.+)$/
@@ -85,7 +86,7 @@ class Chef
         end
 
         def install_package(name, version)
-          shell_out!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+          shell_out_with_timeout!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
         end
 
         def upgrade_package(name, version)
@@ -93,7 +94,7 @@ class Chef
         end
 
         def remove_package(name, version)
-          shell_out!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+          shell_out_with_timeout!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
         end
 
         def purge_package(name, version)
diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb
index 407e0d0..2d63025 100644
--- a/lib/chef/provider/package/paludis.rb
+++ b/lib/chef/provider/package/paludis.rb
@@ -24,6 +24,7 @@ class Chef
     class Package
       class Paludis < Chef::Provider::Package
 
+        provides :package, platform: "exherbo"
         provides :paludis_package, os: "linux"
 
         def load_current_resource
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index bb047ad..95782a6 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -25,6 +25,10 @@ class Chef
   class Provider
     class Package
       class Portage < Chef::Provider::Package
+
+        provides :package, platform: "gentoo"
+        provides :portage_package
+
         PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}
 
         def load_current_resource
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index f10fe23..6ce0dd6 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -17,7 +17,6 @@
 #
 require 'chef/provider/package'
 require 'chef/mixin/command'
-require 'chef/mixin/shell_out'
 require 'chef/resource/package'
 require 'chef/mixin/get_source_from_package'
 
@@ -60,9 +59,9 @@ class Chef
             end
 
             Chef::Log.debug("#{@new_resource} checking rpm status")
-            shell_out!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
+            shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
               case line
-              when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+              when /^(\S+)\s(\S+)$/
                 @current_resource.package_name($1)
                 @new_resource.version($2)
                 @candidate_version = $2
@@ -76,10 +75,10 @@ class Chef
           end
 
           Chef::Log.debug("#{@new_resource} checking install state")
-          @rpm_status = shell_out("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
+          @rpm_status = shell_out_with_timeout("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
           @rpm_status.stdout.each_line do |line|
             case line
-            when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+            when /^(\S+)\s(\S+)$/
               Chef::Log.debug("#{@new_resource} current version is #{$2}")
               @current_resource.version($2)
             end
@@ -90,12 +89,12 @@ class Chef
 
         def install_package(name, version)
           unless @current_resource.version
-            shell_out!( "rpm #{@new_resource.options} -i #{@new_resource.source}" )
+            shell_out_with_timeout!( "rpm #{@new_resource.options} -i #{@new_resource.source}" )
           else
             if allow_downgrade
-              shell_out!( "rpm #{@new_resource.options} -U --oldpackage #{@new_resource.source}" )
+              shell_out_with_timeout!( "rpm #{@new_resource.options} -U --oldpackage #{@new_resource.source}" )
             else
-              shell_out!( "rpm #{@new_resource.options} -U #{@new_resource.source}" )
+              shell_out_with_timeout!( "rpm #{@new_resource.options} -U #{@new_resource.source}" )
             end
           end
         end
@@ -104,9 +103,9 @@ class Chef
 
         def remove_package(name, version)
           if version
-            shell_out!( "rpm #{@new_resource.options} -e #{name}-#{version}" )
+            shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}-#{version}" )
           else
-            shell_out!( "rpm #{@new_resource.options} -e #{name}" )
+            shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}" )
           end
         end
 
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index c53aa89..729f755 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -32,14 +32,7 @@ require 'rubygems/version'
 require 'rubygems/dependency'
 require 'rubygems/spec_fetcher'
 require 'rubygems/platform'
-
-# Compatibility note: Rubygems 2.0 removes rubygems/format in favor of
-# rubygems/package.
-begin
-  require 'rubygems/format'
-rescue LoadError
-  require 'rubygems/package'
-end
+require 'rubygems/package'
 require 'rubygems/dependency_installer'
 require 'rubygems/uninstaller'
 require 'rubygems/specification'
@@ -401,7 +394,7 @@ class Chef
         end
 
         def is_omnibus?
-          if RbConfig::CONFIG['bindir'] =~ %r!/opt/(opscode|chef)/embedded/bin!
+          if RbConfig::CONFIG['bindir'] =~ %r!/(opscode|chef|chefdk)/embedded/bin!
             Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
             # Omnibus installs to a static path because of linking on unix, find it.
             true
@@ -545,9 +538,9 @@ class Chef
             src = @new_resource.source && " --source=#{@new_resource.source} --source=https://rubygems.org"
           end
           if !version.nil? && version.length > 0
-            shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
+            shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
           else
-            shell_out!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
+            shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
           end
         end
 
@@ -571,9 +564,9 @@ class Chef
 
         def uninstall_via_gem_command(name, version)
           if version
-            shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
+            shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
           else
-            shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
+            shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
           end
         end
 
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
index 7cef919..71b8a9b 100644
--- a/lib/chef/provider/package/smartos.rb
+++ b/lib/chef/provider/package/smartos.rb
@@ -29,6 +29,7 @@ class Chef
       class SmartOS < Chef::Provider::Package
         attr_accessor :is_virtual_package
 
+        provides :package, platform: "smartos"
         provides :smartos_package, os: "solaris2", platform_family: "smartos"
 
         def load_current_resource
@@ -43,7 +44,7 @@ class Chef
         def check_package_state(name)
           Chef::Log.debug("#{@new_resource} checking package #{name}")
           version = nil
-          info = shell_out!("/opt/local/sbin/pkg_info -E \"#{name}*\"", :env => nil, :returns => [0,1])
+          info = shell_out_with_timeout!("/opt/local/sbin/pkg_info", "-E", "#{name}*", :env => nil, :returns => [0,1])
 
           if info.stdout
             version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1]
@@ -60,11 +61,11 @@ class Chef
           return @candidate_version if @candidate_version
           name = nil
           version = nil
-          pkg = shell_out!("/opt/local/bin/pkgin se #{new_resource.package_name}", :env => nil, :returns => [0,1])
+          pkg = shell_out_with_timeout!("/opt/local/bin/pkgin", "se", new_resource.package_name, :env => nil, :returns => [0,1])
           pkg.stdout.each_line do |line|
             case line
             when /^#{new_resource.package_name}/
-              name, version = line.split[0].split(/-([^-]+)$/)
+              name, version = line.split(/[; ]/)[0].split(/-([^-]+)$/)
             end
           end
           @candidate_version = version
@@ -74,7 +75,7 @@ class Chef
         def install_package(name, version)
           Chef::Log.debug("#{@new_resource} installing package #{name} version #{version}")
           package = "#{name}-#{version}"
-          out = shell_out!("/opt/local/bin/pkgin -y install #{package}", :env => nil)
+          out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "install", package, :env => nil)
         end
 
         def upgrade_package(name, version)
@@ -85,7 +86,7 @@ class Chef
         def remove_package(name, version)
           Chef::Log.debug("#{@new_resource} removing package #{name} version #{version}")
           package = "#{name}"
-          out = shell_out!("/opt/local/bin/pkgin -y remove #{package}", :env => nil)
+          out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "remove", package, :env => nil)
         end
 
       end
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
index a2cfd93..c0d2bee 100644
--- a/lib/chef/provider/package/solaris.rb
+++ b/lib/chef/provider/package/solaris.rb
@@ -27,6 +27,8 @@ class Chef
 
         include Chef::Mixin::GetSourceFromPackage
 
+        provides :package, platform: "nexentacore"
+        provides :package, platform: "solaris2", platform_version: '< 5.11'
         provides :solaris_package, os: "solaris2"
 
         # def initialize(*args)
@@ -55,7 +57,7 @@ class Chef
             @package_source_found = ::File.exists?(@new_resource.source)
             if @package_source_found
               Chef::Log.debug("#{@new_resource} checking pkg status")
-              shell_out("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}").stdout.each_line do |line|
+              shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}").stdout.each_line do |line|
                 case line
                 when /VERSION:\s+(.+)/
                   @new_resource.version($1)
@@ -65,7 +67,7 @@ class Chef
           end
 
           Chef::Log.debug("#{@new_resource} checking install state")
-          status = shell_out("pkginfo -l #{@current_resource.package_name}")
+          status = shell_out_with_timeout("pkginfo -l #{@current_resource.package_name}")
           status.stdout.each_line do |line|
             case line
             when /VERSION:\s+(.+)/
@@ -87,7 +89,7 @@ class Chef
 
         def candidate_version
           return @candidate_version if @candidate_version
-          status = shell_out("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}")
+          status = shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}")
           status.stdout.each_line do |line|
             case line
             when /VERSION:\s+(.+)/
@@ -110,7 +112,7 @@ class Chef
             else
               command = "pkgadd -n -d #{@new_resource.source} all"
             end
-            shell_out!(command)
+            shell_out_with_timeout!(command)
             Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
           else
             if ::File.directory?(@new_resource.source) # CHEF-4469
@@ -118,17 +120,19 @@ class Chef
             else
               command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
             end
-            shell_out!(command)
+            shell_out_with_timeout!(command)
             Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
           end
         end
 
+        alias_method :upgrade_package, :install_package
+
         def remove_package(name, version)
           if @new_resource.options.nil?
-            shell_out!( "pkgrm -n #{name}" )
+            shell_out_with_timeout!( "pkgrm -n #{name}" )
             Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
           else
-            shell_out!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
+            shell_out_with_timeout!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
             Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
           end
         end
diff --git a/lib/chef/provider/package/windows.rb b/lib/chef/provider/package/windows.rb
index 143d82f..ad2a855 100644
--- a/lib/chef/provider/package/windows.rb
+++ b/lib/chef/provider/package/windows.rb
@@ -16,70 +16,235 @@
 # limitations under the License.
 #
 
+require 'chef/mixin/uris'
 require 'chef/resource/windows_package'
 require 'chef/provider/package'
 require 'chef/util/path_helper'
+require 'chef/mixin/checksum'
 
 class Chef
   class Provider
     class Package
       class Windows < Chef::Provider::Package
+        include Chef::Mixin::Uris
+        include Chef::Mixin::Checksum
 
         provides :package, os: "windows"
         provides :windows_package, os: "windows"
 
-        # Depending on the installer, we may need to examine installer_type or
-        # source attributes, or search for text strings in the installer file
-        # binary to determine the installer type for the user. Since the file
-        # must be on disk to do so, we have to make this choice in the provider.
-        require 'chef/provider/package/windows/msi.rb'
+        require 'chef/provider/package/windows/registry_uninstall_entry.rb'
 
         # load_current_resource is run in Chef::Provider#run_action when not in whyrun_mode?
         def load_current_resource
-          @new_resource.source(Chef::Util::PathHelper.validate_path(@new_resource.source))
-
           @current_resource = Chef::Resource::WindowsPackage.new(@new_resource.name)
-          @current_resource.version(package_provider.installed_version)
-          @new_resource.version(package_provider.package_version)
-          @current_resource
+          if downloadable_file_missing?
+            Chef::Log.debug("We do not know the version of #{new_resource.source} because the file is not downloaded")
+            current_resource.version(:unknown.to_s)
+          else
+            current_resource.version(package_provider.installed_version)
+            new_resource.version(package_provider.package_version)
+          end
+
+          current_resource
         end
 
         def package_provider
           @package_provider ||= begin
             case installer_type
             when :msi
-              Chef::Provider::Package::Windows::MSI.new(@new_resource)
+              Chef::Log.debug("#{@new_resource} is MSI")
+              require 'chef/provider/package/windows/msi'
+              Chef::Provider::Package::Windows::MSI.new(resource_for_provider, uninstall_registry_entries)
             else
-              raise "Unable to find a Chef::Provider::Package::Windows provider for installer_type '#{installer_type}'"
+              Chef::Log.debug("#{@new_resource} is EXE with type '#{installer_type}'")
+              require 'chef/provider/package/windows/exe'
+              Chef::Provider::Package::Windows::Exe.new(resource_for_provider, installer_type, uninstall_registry_entries)
             end
           end
         end
 
         def installer_type
+          # Depending on the installer, we may need to examine installer_type or
+          # source attributes, or search for text strings in the installer file
+          # binary to determine the installer type for the user. Since the file
+          # must be on disk to do so, we have to make this choice in the provider.
           @installer_type ||= begin
             if @new_resource.installer_type
               @new_resource.installer_type
-            else
-              file_extension = ::File.basename(@new_resource.source).split(".").last.downcase
+            elsif source_location.nil?
+                inferred_registry_type
+            else    
+              basename = ::File.basename(source_location)
+              file_extension = basename.split(".").last.downcase
 
               if file_extension == "msi"
                 :msi
               else
-                raise ArgumentError, "Installer type for Windows Package '#{@new_resource.name}' not specified and cannot be determined from file extension '#{file_extension}'"
+                # search the binary file for installer type
+                ::Kernel.open(::File.expand_path(source_location), 'rb') do |io|
+                  filesize = io.size
+                  bufsize = 4096 # read 4K buffers
+                  overlap = 16 # bytes to overlap between buffer reads
+
+                  until io.eof
+                    contents = io.read(bufsize)
+
+                    case contents
+                    when /inno/i # Inno Setup
+                      return :inno
+                    when /wise/i # Wise InstallMaster
+                      return :wise
+                    when /nullsoft/i # Nullsoft Scriptable Install System
+                      return :nsis
+                    end
+
+                    if (io.tell() < filesize)
+                      io.seek(io.tell() - overlap)
+                    end
+                  end
+                 end
+
+                # if file is named 'setup.exe' assume installshield
+                if basename == 'setup.exe'
+                  :installshield
+                else
+                  fail Chef::Exceptions::CannotDetermineWindowsInstallerType, "Installer type for Windows Package '#{@new_resource.name}' not specified and cannot be determined from file extension '#{file_extension}'"
+                end
               end
             end
           end
         end
 
+        def action_install
+          if uri_scheme?(new_resource.source)
+            download_source_file
+            load_current_resource
+          else
+            validate_content!
+          end
+
+          super
+        end
+
         # Chef::Provider::Package action_install + action_remove call install_package + remove_package
         # Pass those calls to the correct sub-provider
         def install_package(name, version)
-          package_provider.install_package(name, version)
+          package_provider.install_package
         end
 
         def remove_package(name, version)
-          package_provider.remove_package(name, version)
+          package_provider.remove_package
+        end
+
+        # @return [Array] new_version(s) as an array
+        def new_version_array
+          # Because the one in the parent caches things
+          [new_resource.version]
+        end
+
+        # @return [String] candidate_version
+        def candidate_version
+          @candidate_version ||= (@new_resource.version || 'latest')
+        end
+
+        # @return [Array] current_version(s) as an array
+        # this package provider does not support package arrays
+        # However, There may be multiple versions for a single 
+        # package so the first element may be a nested array
+        def current_version_array
+          [ current_resource.version ]
+        end
+
+        # @param current_version<String> one or more versions currently installed
+        # @param new_version<String> version of the new resource
+        #
+        # @return [Boolean] true if new_version is equal to or included in current_version
+        def target_version_already_installed?(current_version, new_version)
+          Chef::Log.debug("Checking if #{@new_resource} version '#{new_version}' is already installed. #{current_version} is currently installed")
+          if current_version.is_a?(Array)
+            current_version.include?(new_version)
+          else
+            new_version == current_version
+          end
+        end
+
+        def have_any_matching_version?
+          target_version_already_installed?(current_resource.version, new_resource.version)
+        end
+
+        private
+
+        def uninstall_registry_entries
+          @uninstall_registry_entries ||= Chef::Provider::Package::Windows::RegistryUninstallEntry.find_entries(new_resource.name)
         end
+
+        def inferred_registry_type
+          uninstall_registry_entries.each do |entry|
+            return :inno if entry.key.end_with?("_is1")
+            return :msi if entry.uninstall_string.downcase.start_with?("msiexec.exe ")
+            return :nsis if entry.uninstall_string.downcase.end_with?("uninst.exe\"")
+          end
+          nil
+        end
+
+        def downloadable_file_missing?
+          uri_scheme?(new_resource.source) && !::File.exists?(source_location)
+        end
+
+        def resource_for_provider
+          @resource_for_provider = Chef::Resource::WindowsPackage.new(new_resource.name).tap do |r|
+            r.source(Chef::Util::PathHelper.validate_path(source_location)) unless source_location.nil?
+            r.version(new_resource.version)
+            r.timeout(new_resource.timeout)
+            r.returns(new_resource.returns)
+            r.options(new_resource.options)
+          end
+        end
+
+        def download_source_file
+          source_resource.run_action(:create)
+          Chef::Log.debug("#{@new_resource} fetched source file to #{source_resource.path}")
+        end
+
+        def source_resource
+          @source_resource ||= Chef::Resource::RemoteFile.new(default_download_cache_path, run_context).tap do |r|
+            r.source(new_resource.source)
+            r.checksum(new_resource.checksum)
+            r.backup(false)
+
+            if new_resource.remote_file_attributes
+              new_resource.remote_file_attributes.each do |(k,v)|
+                r.send(k.to_sym, v)
+              end
+            end
+          end
+        end
+
+        def default_download_cache_path
+          uri = ::URI.parse(new_resource.source)
+          filename = ::File.basename(::URI.unescape(uri.path))
+          file_cache_dir = Chef::FileCache.create_cache_path("package/")
+          Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
+        end
+
+        def source_location
+          if uri_scheme?(new_resource.source)
+            source_resource.path
+          else
+            new_source = Chef::Util::PathHelper.cleanpath(new_resource.source)
+            ::File.exist?(new_source) ? new_source : nil
+          end
+        end
+
+        def validate_content!
+          if new_resource.checksum
+            source_checksum = checksum(source_location)
+            if new_resource.checksum != source_checksum
+              raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(source_checksum))
+            end
+          end
+        end
+
       end
     end
   end
diff --git a/lib/chef/provider/package/windows/exe.rb b/lib/chef/provider/package/windows/exe.rb
new file mode 100644
index 0000000..4495868
--- /dev/null
+++ b/lib/chef/provider/package/windows/exe.rb
@@ -0,0 +1,129 @@
+#
+# Author:: Seth Chisamore (<schisamo at chef.io>)
+# Author:: Matt Wrock <matt at mattwrock.com>
+# Copyright:: Copyright (c) 2011, 2015 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/mixin/shell_out'
+
+class Chef
+  class Provider
+    class Package
+      class Windows
+        class Exe
+          include Chef::Mixin::ShellOut
+
+          def initialize(resource, installer_type, uninstall_entries)
+            @new_resource = resource
+            @installer_type = installer_type
+            @uninstall_entries = uninstall_entries
+          end
+
+          attr_reader :new_resource
+          attr_reader :installer_type
+          attr_reader :uninstall_entries
+
+          # From Chef::Provider::Package
+          def expand_options(options)
+            options ? " #{options}" : ""
+          end
+
+          # Returns a version if the package is installed or nil if it is not.
+          def installed_version
+            Chef::Log.debug("#{new_resource} checking package version")
+            current_installed_version
+          end
+
+          def package_version
+            new_resource.version || install_file_version
+          end
+
+          def install_package
+            Chef::Log.debug("#{new_resource} installing #{new_resource.installer_type} package '#{new_resource.source}'")
+            shell_out!(
+              [
+                "start",
+                "\"\"",
+                "/wait",
+                "\"#{new_resource.source}\"",
+                unattended_flags,
+                expand_options(new_resource.options),
+                "& exit %%%%ERRORLEVEL%%%%"
+              ].join(" "), timeout: new_resource.timeout, returns: new_resource.returns
+            )
+          end
+
+          def remove_package
+            uninstall_version = new_resource.version || current_installed_version
+            uninstall_entries.select { |entry| [uninstall_version].flatten.include?(entry.display_version) }
+              .map { |version| version.uninstall_string }.uniq.each do |uninstall_string|
+                Chef::Log.debug("Registry provided uninstall string for #{new_resource} is '#{uninstall_string}'")
+                shell_out!(uninstall_command(uninstall_string), { returns: new_resource.returns })
+              end
+          end
+
+          private
+
+          def uninstall_command(uninstall_string)
+            uninstall_string.delete!('"')
+            uninstall_string = [
+              %q{/d"},
+              ::File.dirname(uninstall_string),
+              %q{" },
+              ::File.basename(uninstall_string),
+              expand_options(new_resource.options),
+              " ",
+              unattended_flags
+            ].join
+            %Q{start "" /wait #{uninstall_string} & exit %%%%ERRORLEVEL%%%%}
+          end
+
+          def current_installed_version
+            @current_installed_version ||= uninstall_entries.count == 0 ? nil : begin
+              uninstall_entries.map { |entry| entry.display_version }.uniq
+            end
+          end
+
+          def install_file_version
+            @install_file_version ||= begin
+              if ::File.exist?(@new_resource.source)
+                version_info = Chef::ReservedNames::Win32::File.version_info(new_resource.source)
+                file_version = version_info.FileVersion || version_info.ProductVersion
+                file_version == '' ? nil : file_version
+              else
+                nil
+              end
+            end
+          end
+
+          # http://unattended.sourceforge.net/installers.php
+          def unattended_flags
+            case installer_type
+            when :installshield
+              '/s /sms'
+            when :nsis
+              '/S /NCRC'
+            when :inno
+              '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART'
+            when :wise
+              '/s'
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb
index 9384529..1cc636b 100644
--- a/lib/chef/provider/package/windows/msi.rb
+++ b/lib/chef/provider/package/windows/msi.rb
@@ -18,7 +18,7 @@
 
 # TODO: Allow @new_resource.source to be a Product Code as a GUID for uninstall / network install
 
-require 'chef/win32/api/installer' if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+require 'chef/win32/api/installer' if (RUBY_PLATFORM =~ /mswin|mingw32|windows/) && Chef::Platform.supports_msi?
 require 'chef/mixin/shell_out'
 
 class Chef
@@ -26,13 +26,17 @@ class Chef
     class Package
       class Windows
         class MSI
-          include Chef::ReservedNames::Win32::API::Installer if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+          include Chef::ReservedNames::Win32::API::Installer if (RUBY_PLATFORM =~ /mswin|mingw32|windows/) && Chef::Platform.supports_msi?
           include Chef::Mixin::ShellOut
 
-          def initialize(resource)
+          def initialize(resource, uninstall_entries)
             @new_resource = resource
+            @uninstall_entries = uninstall_entries
           end
 
+          attr_reader :new_resource
+          attr_reader :uninstall_entries
+
           # From Chef::Provider::Package
           def expand_options(options)
             options ? " #{options}" : ""
@@ -40,27 +44,47 @@ class Chef
 
           # Returns a version if the package is installed or nil if it is not.
           def installed_version
-            Chef::Log.debug("#{@new_resource} getting product code for package at #{@new_resource.source}")
-            product_code = get_product_property(@new_resource.source, "ProductCode")
-            Chef::Log.debug("#{@new_resource} checking package status and version for #{product_code}")
-            get_installed_version(product_code)
+            if ::File.exist?(new_resource.source)
+              Chef::Log.debug("#{new_resource} getting product code for package at #{new_resource.source}")
+              product_code = get_product_property(new_resource.source, "ProductCode")
+              Chef::Log.debug("#{new_resource} checking package status and version for #{product_code}")
+              get_installed_version(product_code)
+            else
+              uninstall_entries.count == 0 ? nil : begin
+                uninstall_entries.map { |entry| entry.display_version }.uniq
+              end
+            end
           end
 
           def package_version
-            Chef::Log.debug("#{@new_resource} getting product version for package at #{@new_resource.source}")
-            get_product_property(@new_resource.source, "ProductVersion")
+            return new_resource.version if new_resource.version
+            if ::File.exist?(new_resource.source)
+              Chef::Log.debug("#{new_resource} getting product version for package at #{new_resource.source}")
+              get_product_property(new_resource.source, "ProductVersion")
+            end
           end
 
-          def install_package(name, version)
+          def install_package
             # We could use MsiConfigureProduct here, but we'll start off with msiexec
-            Chef::Log.debug("#{@new_resource} installing MSI package '#{@new_resource.source}'")
-            shell_out!("msiexec /qn /i \"#{@new_resource.source}\" #{expand_options(@new_resource.options)}", {:timeout => @new_resource.timeout, :returns => @new_resource.returns})
+            Chef::Log.debug("#{new_resource} installing MSI package '#{new_resource.source}'")
+            shell_out!("msiexec /qn /i \"#{new_resource.source}\" #{expand_options(new_resource.options)}", {:timeout => new_resource.timeout, :returns => new_resource.returns})
           end
-  
-          def remove_package(name, version)
+
+          def remove_package
             # We could use MsiConfigureProduct here, but we'll start off with msiexec
-            Chef::Log.debug("#{@new_resource} removing MSI package '#{@new_resource.source}'")
-            shell_out!("msiexec /qn /x \"#{@new_resource.source}\" #{expand_options(@new_resource.options)}", {:timeout => @new_resource.timeout, :returns => @new_resource.returns})
+            if ::File.exist?(new_resource.source)
+              Chef::Log.debug("#{new_resource} removing MSI package '#{new_resource.source}'")
+              shell_out!("msiexec /qn /x \"#{new_resource.source}\" #{expand_options(new_resource.options)}", {:timeout => new_resource.timeout, :returns => new_resource.returns})
+            else
+              uninstall_version = new_resource.version || installed_version
+              uninstall_entries.select { |entry| [uninstall_version].flatten.include?(entry.display_version) }
+                .map { |version| version.uninstall_string }.uniq.each do |uninstall_string|
+                  Chef::Log.debug("#{new_resource} removing MSI package version using '#{uninstall_string}'")
+                  uninstall_string += expand_options(new_resource.options)
+                  uninstall_string += " /Q" unless uninstall_string =~ / \/Q\b/
+                  shell_out!(uninstall_string, {:timeout => new_resource.timeout, :returns => new_resource.returns})
+              end
+            end
           end
         end
       end
diff --git a/lib/chef/provider/package/windows/registry_uninstall_entry.rb b/lib/chef/provider/package/windows/registry_uninstall_entry.rb
new file mode 100644
index 0000000..a63e09c
--- /dev/null
+++ b/lib/chef/provider/package/windows/registry_uninstall_entry.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Seth Chisamore (<schisamo at chef.io>)
+# Author:: Matt Wrock <matt at mattwrock.com>
+# Copyright:: Copyright (c) 2011, 2015 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 'win32/registry' if (RUBY_PLATFORM =~ /mswin|mingw32|windows/)
+
+class Chef
+  class Provider
+    class Package
+      class Windows
+        class RegistryUninstallEntry
+
+          def self.find_entries(package_name)
+            Chef::Log.debug("Finding uninstall entries for #{package_name}")
+            entries = []
+            [
+              [::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0100)],
+              [::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0200)],
+              [::Win32::Registry::HKEY_CURRENT_USER]
+            ].each do |hkey|
+              desired = hkey.length > 1 ? hkey[1] : ::Win32::Registry::Constants::KEY_READ
+              begin
+                ::Win32::Registry.open(hkey[0], UNINSTALL_SUBKEY, desired) do |reg|
+                  reg.each_key do |key, _wtime|
+                    begin
+                      entry = reg.open(key, desired)
+                      display_name = read_registry_property(entry, 'DisplayName')
+                      if display_name == package_name
+                        entries.push(RegistryUninstallEntry.new(hkey, key, entry))
+                      end
+                    rescue ::Win32::Registry::Error => ex
+                      Chef::Log.debug("Registry error opening key '#{key}' on node #{desired}: #{ex}")
+                    end
+                  end
+                end
+              rescue ::Win32::Registry::Error => ex
+                Chef::Log.debug("Registry error opening hive '#{hkey[0]}' :: #{desired}: #{ex}")
+              end
+            end
+            entries
+          end
+
+          def self.read_registry_property(data, property)
+            data[property]
+          rescue ::Win32::Registry::Error => ex
+            Chef::Log.debug("Failure to read property '#{property}'")
+            nil
+          end
+
+          def initialize(hive, key, registry_data)
+            Chef::Log.debug("Creating uninstall entry for #{hive}::#{key}")
+            @hive = hive
+            @key = key
+            @data = registry_data
+            @display_name = RegistryUninstallEntry.read_registry_property(registry_data, 'DisplayName')
+            @display_version = RegistryUninstallEntry.read_registry_property(registry_data, 'DisplayVersion')
+            @uninstall_string = RegistryUninstallEntry.read_registry_property(registry_data, 'UninstallString')
+          end
+
+          attr_reader :hive
+          attr_reader :key
+          attr_reader :display_name
+          attr_reader :display_version
+          attr_reader :uninstall_string
+          attr_reader :data
+
+          private
+
+          UNINSTALL_SUBKEY = 'Software\Microsoft\Windows\CurrentVersion\Uninstall'.freeze
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 49c6f6b..6258472 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1,6 +1,6 @@
-#
+
 # Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,7 @@
 
 require 'chef/config'
 require 'chef/provider/package'
-require 'chef/mixin/shell_out'
+require 'chef/mixin/which'
 require 'chef/resource/package'
 require 'singleton'
 require 'chef/mixin/get_source_from_package'
@@ -28,6 +28,7 @@ class Chef
     class Package
       class Yum < Chef::Provider::Package
 
+        provides :package, platform_family: %w(rhel fedora)
         provides :yum_package, os: "linux"
 
         class RPMUtils
@@ -646,10 +647,12 @@ class Chef
 
         # Cache for our installed and available packages, pulled in from yum-dump.py
         class YumCache
-          include Chef::Mixin::Command
+          include Chef::Mixin::Which
           include Chef::Mixin::ShellOut
           include Singleton
 
+          attr_accessor :yum_binary
+
           def initialize
             @rpmdb = RPMDb.new
 
@@ -713,7 +716,7 @@ class Chef
             status = nil
 
             begin
-              status = shell_out!("/usr/bin/python #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout])
+              status = shell_out!("#{python_bin} #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout])
               status.stdout.each_line do |line|
                 one_line = true
 
@@ -779,6 +782,42 @@ class Chef
             @next_refresh = :none
           end
 
+          def python_bin
+            yum_executable = which(yum_binary)
+            if yum_executable && shabang?(yum_executable)
+              shabang_or_fallback(extract_interpreter(yum_executable))
+            else
+              Chef::Log.warn("Yum executable not found or doesn't start with #!. Using default python.")
+              "/usr/bin/python"
+            end
+          rescue StandardError => e
+            Chef::Log.warn("An error occurred attempting to determine correct python executable. Using default.")
+            Chef::Log.debug(e)
+            "/usr/bin/python"
+          end
+
+          def extract_interpreter(file)
+            ::File.open(file, 'r', &:readline)[2..-1].strip
+          end
+
+          # dnf based systems have a yum shim that has /bin/bash as the interpreter. Don't use this.
+          def shabang_or_fallback(interpreter)
+            if interpreter == '/bin/bash'
+              Chef::Log.warn("Yum executable interpreter is /bin/bash. Falling back to default python.")
+              "/usr/bin/python"
+            else
+              interpreter
+            end
+          end
+
+          def shabang?(file)
+            ::File.open(file, 'r') do |f|
+              f.read(2) == '#!'
+            end
+          rescue Errno::ENOENT
+            false
+          end
+
           def reload
             @next_refresh = :all
           end
@@ -953,11 +992,31 @@ class Chef
           super
 
           @yum = YumCache.instance
+          @yum.yum_binary = yum_binary
+        end
+
+        def yum_binary
+          @yum_binary ||=
+            begin
+              yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
+              yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
+            end
         end
 
         # Extra attributes
         #
 
+        def arch_for_name(n)
+          if @new_resource.respond_to?("arch")
+            @new_resource.arch
+          elsif @arch
+            idx = package_name_array.index(n)
+            as_array(@arch)[idx]
+          else
+            nil
+          end
+        end
+
         def arch
           if @new_resource.respond_to?("arch")
             @new_resource.arch
@@ -966,6 +1025,12 @@ class Chef
           end
         end
 
+        def set_arch(arch)
+          if @new_resource.respond_to?("arch")
+            @new_resource.arch(arch)
+          end
+        end
+
         def flush_cache
           if @new_resource.respond_to?("flush_cache")
             @new_resource.flush_cache
@@ -977,12 +1042,14 @@ class Chef
         # Helpers
         #
 
-        def yum_arch
+        def yum_arch(arch)
           arch ? ".#{arch}" : nil
         end
 
         def yum_command(command)
-          status = shell_out(command, {:timeout => Chef::Config[:yum_timeout]})
+          command = "#{yum_binary} #{command}"
+          Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
+          status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
 
           # This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
           # considered fatal - meaning the rpm is still successfully installed. These issue
@@ -999,7 +1066,7 @@ class Chef
               if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
                 Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
                                "so running install again to verify.")
-                status = shell_out(command, {:timeout => Chef::Config[:yum_timeout]})
+                status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
                 break
               end
             end
@@ -1059,23 +1126,20 @@ class Chef
             end
           end
 
-          # Don't overwrite an existing arch
-          unless arch
-            parse_arch
-          end
 
           @current_resource = Chef::Resource::Package.new(@new_resource.name)
           @current_resource.package_name(@new_resource.package_name)
 
           installed_version = []
           @candidate_version = []
+          @arch = []
           if @new_resource.source
             unless ::File.exists?(@new_resource.source)
               raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
             end
 
             Chef::Log.debug("#{@new_resource} checking rpm status")
-            shell_out!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
+            shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
               case line
               when /([\w\d_.-]+)\s([\w\d_.-]+)/
                 @current_resource.package_name($1)
@@ -1085,24 +1149,43 @@ class Chef
             @candidate_version << @new_resource.version
             installed_version << @yum.installed_version(@current_resource.package_name, arch)
           else
-            if @new_resource.version
-              new_resource = "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch}"
-            else
-              new_resource = "#{@new_resource.package_name}#{yum_arch}"
-            end
 
-            Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+            package_name_array.each_with_index do |pkg, idx|
+              # Don't overwrite an existing arch
+              if arch
+                name, parch = pkg, arch
+              else
+                name, parch = parse_arch(pkg)
+                # if we parsed an arch from the name, update the name
+                # to be just the package name.
+                if parch
+                  if @new_resource.package_name.is_a?(Array)
+                    @new_resource.package_name[idx] = name
+                  else
+                    @new_resource.package_name(name)
+                    # only set the arch if it's a single package
+                    set_arch(parch)
+                  end
+                end
+              end
 
-            package_name_array.each do |pkg|
-              installed_version << @yum.installed_version(pkg, arch)
-              @candidate_version << @yum.candidate_version(pkg, arch)
+              if @new_resource.version
+                new_resource =
+                  "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch(parch)}"
+              else
+                new_resource = "#{@new_resource.package_name}#{yum_arch(parch)}"
+              end
+              Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+              installed_version << @yum.installed_version(name, parch)
+              @candidate_version << @yum.candidate_version(name, parch)
+              @arch << parch
             end
-
           end
 
           if installed_version.size == 1
             @current_resource.version(installed_version[0])
             @candidate_version = @candidate_version[0]
+            @arch = @arch[0]
           else
             @current_resource.version(installed_version)
           end
@@ -1117,7 +1200,7 @@ class Chef
           # Work around yum not exiting with an error if a package doesn't exist
           # for CHEF-2062
           all_avail = as_array(name).zip(as_array(version)).any? do |n, v|
-            @yum.version_available?(n, v, arch)
+            @yum.version_available?(n, v, arch_for_name(n))
           end
           method = log_method = nil
           methods = []
@@ -1159,20 +1242,20 @@ class Chef
 
             repos = []
             pkg_string_bits = []
-            index = 0
             as_array(name).zip(as_array(version)).each do |n, v|
+              idx = package_name_array.index(n)
+              a = arch_for_name(n)
               s = ''
-              unless v == current_version_array[index]
-                s = "#{n}-#{v}#{yum_arch}"
-                repo = @yum.package_repository(n, v, arch)
+              unless v == current_version_array[idx]
+                s = "#{n}-#{v}#{yum_arch(a)}"
+                repo = @yum.package_repository(n, v, a)
                 repos << "#{s} from #{repo} repository"
                 pkg_string_bits << s
               end
-              index += 1
             end
             pkg_string = pkg_string_bits.join(' ')
             Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
-            yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
+            yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
           else
             raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
                                              "and release? (version-release, e.g. 1.84-10.fc6)"
@@ -1181,7 +1264,7 @@ class Chef
 
         def install_package(name, version)
           if @new_resource.source
-            yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
+            yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
           else
             install_remote_package(name, version)
           end
@@ -1219,13 +1302,17 @@ class Chef
 
         def remove_package(name, version)
           if version
-            remove_str = as_array(name).zip(as_array(version)).map do |x|
-              "#{x.join('-')}#{yum_arch}"
+            remove_str = as_array(name).zip(as_array(version)).map do |n, v|
+              a = arch_for_name(n)
+              "#{[n, v].join('-')}#{yum_arch(a)}"
             end.join(' ')
           else
-            remove_str = as_array(name).map { |n| "#{n}#{yum_arch}" }.join(' ')
+            remove_str = as_array(name).map do |n|
+              a = arch_for_name(n)
+              "#{n}#{yum_arch(a)}"
+            end.join(' ')
           end
-          yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
+          yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
 
           if flush_cache[:after]
             @yum.reload
@@ -1240,22 +1327,26 @@ class Chef
 
         private
 
-        def parse_arch
+        def parse_arch(package_name)
           # Allow for foo.x86_64 style package_name like yum uses in it's output
           #
-          if @new_resource.package_name =~ %r{^(.*)\.(.*)$}
+          if package_name =~ %r{^(.*)\.(.*)$}
             new_package_name = $1
             new_arch = $2
             # foo.i386 and foo.beta1 are both valid package names or expressions of an arch.
             # Ensure we don't have an existing package matching package_name, then ensure we at
             # least have a match for the new_package+new_arch before we overwrite. If neither
             # then fall through to standard package handling.
-            if (@yum.installed_version(@new_resource.package_name).nil? and @yum.candidate_version(@new_resource.package_name).nil?) and
-                 (@yum.installed_version(new_package_name, new_arch) or @yum.candidate_version(new_package_name, new_arch))
-               @new_resource.package_name(new_package_name)
-               @new_resource.arch(new_arch)
+            old_installed = @yum.installed_version(package_name)
+            old_candidate = @yum.candidate_version(package_name)
+            new_installed = @yum.installed_version(new_package_name, new_arch)
+            new_candidate = @yum.candidate_version(new_package_name, new_arch)
+            if (old_installed.nil? and old_candidate.nil?) and (new_installed or new_candidate)
+              Chef::Log.debug("Parsed out arch #{new_arch}, new package name is #{new_package_name}")
+              return new_package_name, new_arch
             end
           end
+          return package_name, nil
         end
 
         # If we don't have the package we could have been passed a 'whatprovides' feature
@@ -1300,7 +1391,7 @@ class Chef
             new_package_name = packages.first.name
             new_package_version = packages.first.version.to_s
             debug_msg = "#{name}: Unable to match package '#{name}' but matched #{packages.size} "
-            debug_msg << packages.size == 1 ? "package" : "packages"
+            debug_msg << (packages.size == 1 ? "package" : "packages")
             debug_msg << ", selected '#{new_package_name}' version '#{new_package_version}'"
             Chef::Log.debug(debug_msg)
 
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
index 2cd3216..ac42304 100644
--- a/lib/chef/provider/package/zypper.rb
+++ b/lib/chef/provider/package/zypper.rb
@@ -29,46 +29,49 @@ class Chef
     class Package
       class Zypper < Chef::Provider::Package
 
+        provides :package, platform_family: "suse"
+        provides :zypper_package, os: "linux"
+
         def load_current_resource
-          @current_resource = Chef::Resource::Package.new(@new_resource.name)
-          @current_resource.package_name(@new_resource.package_name)
+          @current_resource = Chef::Resource::ZypperPackage.new(new_resource.name)
+          current_resource.package_name(new_resource.package_name)
 
           is_installed=false
           is_out_of_date=false
           version=''
           oud_version=''
-          Chef::Log.debug("#{@new_resource} checking zypper")
-          status = shell_out("zypper --non-interactive info #{@new_resource.package_name}")
+          Chef::Log.debug("#{new_resource} checking zypper")
+          status = shell_out_with_timeout("zypper --non-interactive info #{new_resource.package_name}")
           status.stdout.each_line do |line|
             case line
             when /^Version: (.+)$/
               version = $1
-              Chef::Log.debug("#{@new_resource} version #{$1}")
+              Chef::Log.debug("#{new_resource} version #{$1}")
             when /^Installed: Yes$/
               is_installed=true
-              Chef::Log.debug("#{@new_resource} is installed")
+              Chef::Log.debug("#{new_resource} is installed")
 
             when /^Installed: No$/
               is_installed=false
-              Chef::Log.debug("#{@new_resource} is not installed")
+              Chef::Log.debug("#{new_resource} is not installed")
             when /^Status: out-of-date \(version (.+) installed\)$/
               is_out_of_date=true
               oud_version=$1
-              Chef::Log.debug("#{@new_resource} out of date version #{$1}")
+              Chef::Log.debug("#{new_resource} out of date version #{$1}")
             end
           end
 
           if is_installed==false
             @candidate_version=version
-            @current_resource.version(nil)
+            current_resource.version(nil)
           end
 
           if is_installed==true
             if is_out_of_date==true
-              @current_resource.version(oud_version)
+              current_resource.version(oud_version)
               @candidate_version=version
             else
-              @current_resource.version(version)
+              current_resource.version(version)
               @candidate_version=version
             end
           end
@@ -77,7 +80,7 @@ class Chef
             raise Chef::Exceptions::Package, "zypper failed - #{status.inspect}!"
           end
 
-          @current_resource
+          current_resource
         end
 
         def zypper_version()
@@ -104,9 +107,9 @@ class Chef
         def zypper_package(command, pkgname, version)
           version = "=#{version}" unless version.nil? || version.empty?
           if zypper_version < 1.0
-            shell_out!("zypper#{gpg_checks} #{command} -y #{pkgname}")
+            shell_out_with_timeout!("zypper#{gpg_checks} #{command} -y #{pkgname}")
           else
-            shell_out!("zypper --non-interactive#{gpg_checks} "+
+            shell_out_with_timeout!("zypper --non-interactive#{gpg_checks} "+
                       "#{command} #{pkgname}#{version}")
           end
         end
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index f9dcd6d..e04efb6 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -16,6 +16,7 @@
 # limitations under the License.
 #
 
+require 'chef/platform/query_helpers'
 require 'chef/provider/windows_script'
 
 class Chef
@@ -24,71 +25,191 @@ class Chef
 
       provides :powershell_script, os: "windows"
 
+      def initialize (new_resource, run_context)
+        super(new_resource, run_context, '.ps1')
+        add_exit_status_wrapper
+      end
+
+      def action_run
+        validate_script_syntax!
+        super
+      end
+
+      def command
+        basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+
+        # Powershell.exe is always in "v1.0" folder (for backwards compatibility)
+        interpreter_path = Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", interpreter)
+
+        # Must use -File rather than -Command to launch the script
+        # file created by the base class that contains the script
+        # code -- otherwise, powershell.exe does not propagate the
+        # error status of a failed Windows process that ran at the
+        # end of the script, it gets changed to '1'.
+        #
+        # Nano only supports -Command
+        cmd = "\"#{interpreter_path}\" #{flags}"
+        if Chef::Platform.windows_nano_server?
+          cmd << " -Command \". '#{script_file.path}'\""
+        else
+          cmd << " -File \"#{script_file.path}\""
+        end
+        cmd
+      end
+
+      def flags
+        interpreter_flags = [*default_interpreter_flags].join(' ')
+
+        if ! (@new_resource.flags.nil?)
+          interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
+        end
+
+        interpreter_flags
+      end
+
       protected
-      EXIT_STATUS_EXCEPTION_HANDLER = "\ntrap [Exception] {write-error -exception ($_.Exception.Message);exit 1}".freeze
-      EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -ne $true) { if ( $LASTEXITCODE ) {exit $LASTEXITCODE} else { exit 1 }}".freeze
-      EXIT_STATUS_RESET_SCRIPT = "\n$global:LASTEXITCODE=$null".freeze
 
-      # Process exit codes are strange with PowerShell. Unless you
-      # explicitly call exit in Powershell, the powershell.exe
-      # interpreter returns only 0 for success or 1 for failure. Since
-      # we'd like to get specific exit codes from executable tools run
-      # with Powershell, we do some work using the automatic variables
-      # $? and $LASTEXITCODE to return the process exit code of the
-      # last process run in the script if it is the last command
-      # executed, otherwise 0 or 1 based on whether $? is set to true
-      # (success, where we return 0) or false (where we return 1).
-      def normalize_script_exit_status( code )
-        target_code = ( EXIT_STATUS_EXCEPTION_HANDLER +
-                        EXIT_STATUS_RESET_SCRIPT +
-                        "\n" +
-                        code.to_s +
-                        EXIT_STATUS_NORMALIZATION_SCRIPT )
-        convert_boolean_return = @new_resource.convert_boolean_return
-        self.code = <<EOH
-new-variable -name interpolatedexitcode -visibility private -value $#{convert_boolean_return}
-new-variable -name chefscriptresult -visibility private
-$chefscriptresult = {
-#{target_code}
-}.invokereturnasis()
-if ($interpolatedexitcode -and $chefscriptresult.gettype().name -eq 'boolean') { exit [int32](!$chefscriptresult) } else { exit 0 }
-EOH
-        Chef::Log.debug("powershell_script provider called with script code:\n\n#{code}\n")
+      # Process exit codes are strange with PowerShell and require
+      # special handling to cover common use cases.
+      def add_exit_status_wrapper
+        self.code = wrapper_script
+        Chef::Log.debug("powershell_script provider called with script code:\n\n#{@new_resource.code}\n")
         Chef::Log.debug("powershell_script provider will execute transformed code:\n\n#{self.code}\n")
       end
 
-      public
+      def validate_script_syntax!
+        interpreter_arguments = default_interpreter_flags.join(' ')
+        Tempfile.open(['chef_powershell_script-user-code', '.ps1']) do | user_script_file |
+          # Wrap the user's code in a PowerShell script block so that
+          # it isn't executed. However, syntactically invalid script
+          # in that block will still trigger a syntax error which is
+          # exactly what we want here -- verify the syntax without
+          # actually running the script.
+          user_code_wrapped_in_powershell_script_block = <<-EOH
+{
+  #{@new_resource.code}
+}
+EOH
+          user_script_file.puts user_code_wrapped_in_powershell_script_block
 
-      def initialize (new_resource, run_context)
-        super(new_resource, run_context, '.ps1')
-        normalize_script_exit_status(new_resource.code)
+          # A .close or explicit .flush required to ensure the file is
+          # written to the file system at this point, which is required since
+          # the intent is to execute the code just written to it.
+          user_script_file.close
+          validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command \". '#{user_script_file.path}'\""
+
+          # Note that other script providers like bash allow syntax errors
+          # to be suppressed by setting 'returns' to a value that the
+          # interpreter would return as a status code in the syntax
+          # error case. We explicitly don't do this here -- syntax
+          # errors will not be suppressed, since doing so could make
+          # it harder for users to detect / debug invalid scripts.
+
+          # Therefore, the only return value for a syntactically valid
+          # script is 0. If an exception is raised by shellout, this
+          # means a non-zero return and thus a syntactically invalid script.
+
+          with_os_architecture(node, architecture: new_resource.architecture) do
+            shell_out!(validation_command, {returns: [0]})
+          end
+        end
       end
 
-      def flags
-        default_flags = [
+      def default_interpreter_flags
+        return [] if Chef::Platform.windows_nano_server?
+
+        # Execution policy 'Bypass' is preferable since it doesn't require
+        # user input confirmation for files such as PowerShell modules
+        # downloaded from the Internet. However, 'Bypass' is not supported
+        # prior to PowerShell 3.0, so the fallback is 'Unrestricted'
+        execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? 'Bypass' : 'Unrestricted'
+
+        [
           "-NoLogo",
           "-NonInteractive",
           "-NoProfile",
-          "-ExecutionPolicy Unrestricted",
+          "-ExecutionPolicy #{execution_policy}",
           # Powershell will hang if STDIN is redirected
           # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
-          "-InputFormat None",
-          # Must use -File rather than -Command to launch the script
-          # file created by the base class that contains the script
-          # code -- otherwise, powershell.exe does not propagate the
-          # error status of a failed Windows process that ran at the
-          # end of the script, it gets changed to '1'.
-          "-File"
+          "-InputFormat None"
         ]
+      end
 
-        interpreter_flags = default_flags.join(' ')
+      # A wrapper script is used to launch user-supplied script while
+      # still obtaining useful process exit codes. Unless you
+      # explicitly call exit in Powershell, the powershell.exe
+      # interpreter returns only 0 for success or 1 for failure. Since
+      # we'd like to get specific exit codes from executable tools run
+      # with Powershell, we do some work using the automatic variables
+      # $? and $LASTEXITCODE to return the process exit code of the
+      # last process run in the script if it is the last command
+      # executed, otherwise 0 or 1 based on whether $? is set to true
+      # (success, where we return 0) or false (where we return 1).
+      def wrapper_script
+<<-EOH
+# Chef Client wrapper for powershell_script resources
 
-        if ! (@new_resource.flags.nil?)
-          interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
-        end
+# LASTEXITCODE can be uninitialized -- make it explictly 0
+# to avoid incorrect detection of failure (non-zero) codes
+$global:LASTEXITCODE = 0
 
-        interpreter_flags
+# Catch any exceptions -- without this, exceptions will result
+# In a zero return code instead of the desired non-zero code
+# that indicates a failure
+trap [Exception] {write-error ($_.Exception.Message);exit 1}
+
+# Variable state that should not be accessible to the user code
+new-variable -name interpolatedexitcode -visibility private -value $#{@new_resource.convert_boolean_return}
+new-variable -name chefscriptresult -visibility private
+
+# Initialize a variable we use to capture $? inside a block
+$global:lastcmdlet = $null
+
+# Execute the user's code in a script block --
+$chefscriptresult =
+{
+ #{@new_resource.code}
+
+ # This assignment doesn't affect the block's return value
+ $global:lastcmdlet = $?
+}.invokereturnasis()
+
+# Assume failure status of 1 -- success cases
+# will have to override this
+$exitstatus = 1
+
+# If convert_boolean_return is enabled, the block's return value
+# gets precedence in determining our exit status
+if ($interpolatedexitcode -and $chefscriptresult -ne $null -and $chefscriptresult.gettype().name -eq 'boolean')
+{
+  $exitstatus = [int32](!$chefscriptresult)
+}
+elseif ($lastcmdlet)
+{
+  # Otherwise, a successful cmdlet execution defines the status
+  $exitstatus = 0
+}
+elseif ( $LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0 )
+{
+  # If the cmdlet status is failed, allow the Win32 status
+  # in $LASTEXITCODE to define exit status. This handles the case
+  # where no cmdlets, only Win32 processes have run since $?
+  # will be set to $false whenever a Win32 process returns a non-zero
+  # status.
+  $exitstatus = $LASTEXITCODE
+}
+
+# Print STDOUT for the script execution
+Write-Output $chefscriptresult
+
+# If this script is launched with -File, the process exit
+# status of PowerShell.exe will be $exitstatus. If it was
+# launched with -Command, it will be 0 if $exitstatus was 0,
+# 1 (i.e. failed) otherwise.
+exit $exitstatus
+EOH
       end
+
     end
   end
 end
diff --git a/lib/chef/provider/reboot.rb b/lib/chef/provider/reboot.rb
index 8dde465..22e77dc 100644
--- a/lib/chef/provider/reboot.rb
+++ b/lib/chef/provider/reboot.rb
@@ -22,6 +22,7 @@ require 'chef/provider'
 class Chef
   class Provider
     class Reboot < Chef::Provider
+      provides :reboot
 
       def whyrun_supported?
         true
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index 94f4e26..948fa6c 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -31,6 +31,8 @@ class Chef
 
   class Provider
     class RegistryKey < Chef::Provider
+      provides :registry_key
+
       include Chef::Mixin::Checksum
 
       def whyrun_supported?
@@ -62,7 +64,7 @@ class Chef
 
       def values_to_hash(values)
         if values
-          @name_hash = Hash[values.map { |val| [val[:name], val] }]
+          @name_hash = Hash[values.map { |val| [val[:name].downcase, val] }]
         else
           @name_hash = {}
         end
@@ -98,8 +100,8 @@ class Chef
           end
         end
         @new_resource.unscrubbed_values.each do |value|
-          if @name_hash.has_key?(value[:name])
-            current_value = @name_hash[value[:name]]
+          if @name_hash.has_key?(value[:name].downcase)
+            current_value = @name_hash[value[:name].downcase]
             unless current_value[:type] == value[:type] && current_value[:data] == value[:data]
               converge_by("set value #{value}") do
                 registry.set_value(@new_resource.key, value)
@@ -120,7 +122,7 @@ class Chef
           end
         end
         @new_resource.unscrubbed_values.each do |value|
-          unless @name_hash.has_key?(value[:name])
+          unless @name_hash.has_key?(value[:name].downcase)
             converge_by("create value #{value}") do
               registry.set_value(@new_resource.key, value)
             end
@@ -131,7 +133,7 @@ class Chef
       def action_delete
         if registry.key_exists?(@new_resource.key)
           @new_resource.unscrubbed_values.each do |value|
-            if @name_hash.has_key?(value[:name])
+            if @name_hash.has_key?(value[:name].downcase)
               converge_by("delete value #{value}") do
                 registry.delete_value(@new_resource.key, value)
               end
diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb
index eaccce4..3c1c50b 100644
--- a/lib/chef/provider/remote_directory.rb
+++ b/lib/chef/provider/remote_directory.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,178 +16,266 @@
 # limitations under the License.
 #
 
-require 'chef/provider/file'
 require 'chef/provider/directory'
+require 'chef/resource/file'
 require 'chef/resource/directory'
-require 'chef/resource/remote_file'
+require 'chef/resource/cookbook_file'
 require 'chef/mixin/file_class'
-require 'chef/platform'
-require 'uri'
-require 'tempfile'
-require 'net/https'
-require 'set'
+require 'chef/platform/query_helpers'
 require 'chef/util/path_helper'
+require 'chef/deprecation/warnings'
+require 'chef/deprecation/provider/remote_directory'
+
+require 'forwardable'
 
 class Chef
   class Provider
     class RemoteDirectory < Chef::Provider::Directory
+      extend Forwardable
+      include Chef::Mixin::FileClass
 
       provides :remote_directory
 
-      include Chef::Mixin::FileClass
+      def_delegators :@new_resource, :purge, :path, :source, :cookbook, :cookbook_name
+      def_delegators :@new_resource, :files_rights, :files_mode, :files_group, :files_owner, :files_backup
+      def_delegators :@new_resource, :rights, :mode, :group, :owner
+
+      # The overwrite property on the resource.  Delegates to new_resource but can be mutated.
+      #
+      # @return [Boolean] if we are overwriting
+      #
+      def overwrite?
+        @overwrite = new_resource.overwrite if @overwrite.nil?
+        !!@overwrite
+      end
+
+      attr_accessor :managed_files
+
+      # Hash containing keys of the paths for all the files that we sync, plus all their
+      # parent directories.
+      #
+      # @return [Set] Ruby Set of the files that we manage
+      #
+      def managed_files
+        @managed_files ||= Set.new
+      end
 
+      # Handle action :create.
+      #
       def action_create
         super
-        # Mark all files as needing to be purged
-        files_to_purge = Set.new(ls(@new_resource.path)) # Make sure each path is clean
 
         # Transfer files
         files_to_transfer.each do |cookbook_file_relative_path|
           create_cookbook_file(cookbook_file_relative_path)
-          # parent directories and file being transferred are removed from the purge list
-          Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(@new_resource.path, cookbook_file_relative_path))).descend do |d|
-            files_to_purge.delete(d.to_s)
-          end
+          # parent directories and file being transferred need to not be removed in the purge
+          add_managed_file(cookbook_file_relative_path)
         end
 
-        purge_unmanaged_files(files_to_purge)
+        purge_unmanaged_files
       end
 
+      # Handle action :create_if_missing.
+      #
       def action_create_if_missing
         # if this action is called, ignore the existing overwrite flag
-        @new_resource.overwrite(false)
+        @overwrite = false
         action_create
       end
 
-      protected
+      private
 
-      # List all excluding . and ..
-      def ls(path)
-        files = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'),
-                 ::File::FNM_DOTMATCH)
-
-        # Remove current directory and previous directory
-        files.reject! do |name|
-          basename = Pathname.new(name).basename().to_s
-          ['.', '..'].include?(basename)
+      # Add a file and its parent directories to the managed_files Hash.
+      #
+      # @param [String] cookbook_file_relative_path relative path to the file
+      # @api private
+      #
+      def add_managed_file(cookbook_file_relative_path)
+        if purge
+          Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(path, cookbook_file_relative_path))).descend do |d|
+            managed_files.add(d.to_s)
+          end
         end
-
-        # Clean all the paths... this is required because of the join
-        files.map {|f| Chef::Util::PathHelper.cleanpath(f)}
       end
 
-      def purge_unmanaged_files(unmanaged_files)
-        if @new_resource.purge
-          unmanaged_files.sort.reverse.each do |f|
-            # file_class comes from Chef::Mixin::FileClass
-            if ::File.directory?(f) && !Chef::Platform.windows? && !file_class.symlink?(f.dup)
-              # Linux treats directory symlinks as files
-              # Remove a directory as a directory when not on windows if it is not a symlink
-              purge_directory(f)
-            elsif ::File.directory?(f) && Chef::Platform.windows?
-              # Windows treats directory symlinks as directories so we delete them here
-              purge_directory(f)
-            else
-              converge_by("delete unmanaged file #{f}") do
-                ::File.delete(f)
-                Chef::Log.debug("#{@new_resource} deleted file #{f}")
+      # Remove all files not in the managed_files Set.
+      #
+      # @api private
+      #
+      def purge_unmanaged_files
+        if purge
+          Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'), ::File::FNM_DOTMATCH).sort!.reverse!.each do |file|
+            # skip '.' and '..'
+            next if ['.','..'].include?(Pathname.new(file).basename().to_s)
+
+            # Clean the path.  This is required because of the ::File.join
+            file = Chef::Util::PathHelper.cleanpath(file)
+
+            # Skip files that we've sync'd and their parent dirs
+            next if managed_files.include?(file)
+
+            if ::File.directory?(file)
+              if !Chef::Platform.windows? && file_class.symlink?(file.dup)
+                # Unix treats dir symlinks as files
+                purge_file(file)
+              else
+                # Unix dirs are dirs, Windows dirs and dir symlinks are dirs
+                purge_directory(file)
               end
+            else
+              purge_file(file)
             end
           end
         end
       end
 
+      # Use a Chef directory sub-resource to remove a directory.
+      #
+      # @param [String] dir The path of the directory to remove
+      # @api private
+      #
       def purge_directory(dir)
-        converge_by("delete unmanaged directory #{dir}") do
-          Dir::rmdir(dir)
-          Chef::Log.debug("#{@new_resource} removed directory #{dir}")
-        end
+        res = Chef::Resource::Directory.new(dir, run_context)
+        res.run_action(:delete)
+        new_resource.updated_by_last_action(true) if res.updated?
       end
 
+      # Use a Chef file sub-resource to remove a file.
+      #
+      # @param [String] file The path of the file to remove
+      # @api private
+      #
+      def purge_file(file)
+        res = Chef::Resource::File.new(file, run_context)
+        res.run_action(:delete)
+        new_resource.updated_by_last_action(true) if res.updated?
+      end
+
+      # Get the files to tranfer.  This returns files in lexicographical sort order.
+      #
+      # FIXME: it should do breadth-first, see CHEF-5080 (please use a performant sort)
+      #
+      # @return Array<String> The list of files to transfer
+      # @api private
+      #
       def files_to_transfer
         cookbook = run_context.cookbook_collection[resource_cookbook]
-        files = cookbook.relative_filenames_in_preferred_directory(node, :files, @new_resource.source)
-        files.sort.reverse
+        files = cookbook.relative_filenames_in_preferred_directory(node, :files, source)
+        files.sort_by! { |x| x.count(::File::SEPARATOR) }
       end
 
-      def directory_root_in_cookbook_cache
-        @directory_root_in_cookbook_cache ||= begin
-          cookbook = run_context.cookbook_collection[resource_cookbook]
-          cookbook.preferred_filename_on_disk_location(node, :files, @new_resource.source, @new_resource.path)
-        end
+      # Either the explicit cookbook that the user sets on the resource, or the implicit
+      # cookbook_name that the resource was declared in.
+      #
+      # @return [String] Cookbook to get file from.
+      # @api private
+      #
+      def resource_cookbook
+        cookbook || cookbook_name
       end
 
-      # Determine the cookbook to get the file from. If new resource sets an
-      # explicit cookbook, use it, otherwise fall back to the implicit cookbook
-      # i.e., the cookbook the resource was declared in.
-      def resource_cookbook
-        @new_resource.cookbook || @new_resource.cookbook_name
+      # If we are overwriting, then cookbook_file sub-resources should all be action :create,
+      # otherwise they should be :create_if_missing
+      #
+      # @return [Symbol] Action to take on cookbook_file sub-resources
+      # @api private
+      #
+      def action_for_cookbook_file
+        overwrite? ? :create : :create_if_missing
       end
 
+      # This creates and uses a cookbook_file resource to sync a single file from the cookbook.
+      #
+      # @param [String] cookbook_file_relative_path The relative path to the cookbook file
+      # @api private
+      #
       def create_cookbook_file(cookbook_file_relative_path)
-        full_path = ::File.join(@new_resource.path, cookbook_file_relative_path)
+        full_path = ::File.join(path, cookbook_file_relative_path)
 
         ensure_directory_exists(::File.dirname(full_path))
 
-        file_to_fetch = cookbook_file_resource(full_path, cookbook_file_relative_path)
-        if @new_resource.overwrite
-          file_to_fetch.run_action(:create)
-        else
-          file_to_fetch.run_action(:create_if_missing)
-        end
-        @new_resource.updated_by_last_action(true) if file_to_fetch.updated?
+        res = cookbook_file_resource(full_path, cookbook_file_relative_path)
+        res.run_action(action_for_cookbook_file)
+        new_resource.updated_by_last_action(true) if res.updated?
       end
 
+      # This creates the cookbook_file resource for use by create_cookbook_file.
+      #
+      # @param [String] target_path Path on the system to create
+      # @param [String] relative_source_path Relative path in the cookbook to the base source
+      # @return [Chef::Resource::CookbookFile] The built cookbook_file resource
+      # @api private
+      #
       def cookbook_file_resource(target_path, relative_source_path)
-        cookbook_file = Chef::Resource::CookbookFile.new(target_path, run_context)
-        cookbook_file.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name
-        cookbook_file.source(::File.join(@new_resource.source, relative_source_path))
-        if Chef::Platform.windows? && @new_resource.files_rights
-          @new_resource.files_rights.each_pair do |permission, *args|
-            cookbook_file.rights(permission, *args)
+        res = Chef::Resource::CookbookFile.new(target_path, run_context)
+        res.cookbook_name = resource_cookbook
+        res.source(::File.join(source, relative_source_path))
+        if Chef::Platform.windows? && files_rights
+          files_rights.each_pair do |permission, *args|
+            res.rights(permission, *args)
           end
         end
-        cookbook_file.mode(@new_resource.files_mode)       if @new_resource.files_mode
-        cookbook_file.group(@new_resource.files_group)     if @new_resource.files_group
-        cookbook_file.owner(@new_resource.files_owner)     if @new_resource.files_owner
-        cookbook_file.backup(@new_resource.files_backup)   if @new_resource.files_backup
+        res.mode(files_mode)       if files_mode
+        res.group(files_group)     if files_group
+        res.owner(files_owner)     if files_owner
+        res.backup(files_backup)   if files_backup
 
-        cookbook_file
+        res
       end
 
-      def ensure_directory_exists(path)
-        unless ::File.directory?(path)
-          directory_to_create = resource_for_directory(path)
-          directory_to_create.run_action(:create)
-          @new_resource.updated_by_last_action(true) if directory_to_create.updated?
+      # This creates and uses a directory resource to create a directory if it is needed.
+      #
+      # @param [String] dir The path to the directory to create.
+      # @api private
+      #
+      def ensure_directory_exists(dir)
+        # doing the check here and skipping the resource should be more performant
+        unless ::File.directory?(dir)
+          res = directory_resource(dir)
+          res.run_action(:create)
+          new_resource.updated_by_last_action(true) if res.updated?
         end
       end
 
-      def resource_for_directory(path)
-        dir = Chef::Resource::Directory.new(path, run_context)
-        dir.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name
-        if Chef::Platform.windows? && @new_resource.rights
+      # This creates the directory resource for ensure_directory_exists.
+      #
+      # @param [String] dir Directory path on the system
+      # @return [Chef::Resource::Directory] The built directory resource
+      # @api private
+      #
+      def directory_resource(dir)
+        res = Chef::Resource::Directory.new(dir, run_context)
+        res.cookbook_name = resource_cookbook
+        if Chef::Platform.windows? && rights
           # rights are only meant to be applied to the toppest-level directory;
           # Windows will handle inheritance.
-          if path == @new_resource.path
-            @new_resource.rights.each do |rights| #rights is a hash
-              permissions = rights.delete(:permissions) #delete will return the value or nil if not found
-              principals = rights.delete(:principals)
-              dir.rights(permissions, principals, rights)
+          if dir == path
+            rights.each do |r|
+              r = r.dup  # do not update the new_resource
+              permissions = r.delete(:permissions)
+              principals = r.delete(:principals)
+              res.rights(permissions, principals, r)
             end
           end
         end
-        dir.mode(@new_resource.mode) if @new_resource.mode
-        dir.group(@new_resource.group)
-        dir.owner(@new_resource.owner)
-        dir.recursive(true)
-        dir
-      end
+        res.mode(mode) if mode
+        res.group(group) if group
+        res.owner(owner) if owner
+        res.recursive(true)
 
-      def whyrun_supported?
-        true
+        res
       end
 
+      #
+      # Add back deprecated methods and aliases that are internally unused and should be removed in Chef-13
+      #
+      extend Chef::Deprecation::Warnings
+      include Chef::Deprecation::Provider::RemoteDirectory
+      add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteDirectory.instance_methods)
+
+      alias_method :resource_for_directory, :directory_resource
+      add_deprecation_warnings_for([:resource_for_directory])
+
     end
   end
 end
diff --git a/lib/chef/provider/remote_file.rb b/lib/chef/provider/remote_file.rb
index da2573d..c4643ed 100644
--- a/lib/chef/provider/remote_file.rb
+++ b/lib/chef/provider/remote_file.rb
@@ -24,6 +24,7 @@ require 'chef/deprecation/warnings'
 class Chef
   class Provider
     class RemoteFile < Chef::Provider::File
+      provides :remote_file
 
       extend Chef::Deprecation::Warnings
       include Chef::Deprecation::Provider::RemoteFile
diff --git a/lib/chef/provider/remote_file/cache_control_data.rb b/lib/chef/provider/remote_file/cache_control_data.rb
index f9b7293..3f39dac 100644
--- a/lib/chef/provider/remote_file/cache_control_data.rb
+++ b/lib/chef/provider/remote_file/cache_control_data.rb
@@ -145,18 +145,51 @@ class Chef
         end
 
         def load_json_data
-          Chef::FileCache.load("remote_file/#{sanitized_cache_file_basename}")
+          path = sanitized_cache_file_path(sanitized_cache_file_basename)
+          if Chef::FileCache.has_key?(path)
+            Chef::FileCache.load(path)
+          else
+            old_path = sanitized_cache_file_path(sanitized_cache_file_basename_md5)
+            if Chef::FileCache.has_key?(old_path)
+              # We found an old cache control data file. We started using sha256 instead of md5
+              # to name these. Upgrade the file to the new name.
+              Chef::Log.debug("Found old cache control data file at #{old_path}. Moving to #{path}.")
+              Chef::FileCache.load(old_path).tap do |data|
+                Chef::FileCache.store(path, data)
+                Chef::FileCache.delete(old_path)
+              end
+            else
+              raise Chef::Exceptions::FileNotFound
+            end
+          end
         end
 
-        def sanitized_cache_file_basename
+        def sanitized_cache_file_path(basename)
+          "remote_file/#{basename}"
+        end
+
+        def scrubbed_uri
           # Scrub and truncate in accordance with the goals of keeping the name
           # human-readable but within the bounds of local file system
           # path length limits
-          scrubbed_uri = uri.gsub(/\W/, '_')[0..63]
+          uri.gsub(/\W/, '_')[0..63]
+        end
+
+        def sanitized_cache_file_basename
+          uri_sha2 = Chef::Digester.instance.generate_checksum(StringIO.new(uri))
+          cache_file_basename(uri_sha2[0,32])
+        end
+
+
+        def sanitized_cache_file_basename_md5
+          # Old way of creating the file basename
           uri_md5 = Chef::Digester.instance.generate_md5_checksum(StringIO.new(uri))
-          "#{scrubbed_uri}-#{uri_md5}.json"
+          cache_file_basename(uri_md5)
         end
 
+        def cache_file_basename(checksum)
+          "#{scrubbed_uri}-#{checksum}.json"
+        end
       end
     end
   end
diff --git a/lib/chef/provider/remote_file/content.rb b/lib/chef/provider/remote_file/content.rb
index ef55dd7..4f450ce 100644
--- a/lib/chef/provider/remote_file/content.rb
+++ b/lib/chef/provider/remote_file/content.rb
@@ -20,6 +20,7 @@
 require 'uri'
 require 'tempfile'
 require 'chef/file_content_management/content_base'
+require 'chef/mixin/uris'
 
 class Chef
   class Provider
@@ -28,6 +29,8 @@ class Chef
 
         private
 
+        include Chef::Mixin::Uris
+
         def file_for_provider
           Chef::Log.debug("#{@new_resource} checking for changes")
 
@@ -45,7 +48,11 @@ class Chef
           sources = sources.dup
           source = sources.shift
           begin
-            uri = URI.parse(source)
+            uri = if Chef::Provider::RemoteFile::Fetcher.network_share?(source)
+              source
+            else
+              as_uri(source)
+            end
             raw_file = grab_file_from_uri(uri)
           rescue SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPServerException, Net::HTTPFatalError, Net::FTPError => e
             Chef::Log.warn("#{@new_resource} cannot be downloaded from #{source}: #{e.to_s}")
diff --git a/lib/chef/provider/remote_file/fetcher.rb b/lib/chef/provider/remote_file/fetcher.rb
index 249b291..53bfe99 100644
--- a/lib/chef/provider/remote_file/fetcher.rb
+++ b/lib/chef/provider/remote_file/fetcher.rb
@@ -23,15 +23,29 @@ class Chef
       class Fetcher
 
         def self.for_resource(uri, new_resource, current_resource)
-          case uri.scheme
-          when "http", "https"
-            Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource)
-          when "ftp"
-            Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource)
-          when "file"
-            Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource)
+          if network_share?(uri)
+            Chef::Provider::RemoteFile::NetworkFile.new(uri, new_resource, current_resource)
           else
-            raise ArgumentError, "Invalid uri, Only http(s), ftp, and file are currently supported"
+            case uri.scheme
+            when "http", "https"
+              Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource)
+            when "ftp"
+              Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource)
+            when "file"
+              Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource)
+            else
+              raise ArgumentError, "Invalid uri, Only http(s), ftp, and file are currently supported"
+            end
+          end
+        end
+
+        # Windows network share: \\computername\share\file
+        def self.network_share?(source)
+          case source
+          when String
+            !!(%r{\A\\\\[A-Za-z0-9+\-\.]+} =~ source)
+          else
+            false
           end
         end
 
diff --git a/lib/chef/provider/remote_file/http.rb b/lib/chef/provider/remote_file/http.rb
index f17ab5a..e1f1cb2 100644
--- a/lib/chef/provider/remote_file/http.rb
+++ b/lib/chef/provider/remote_file/http.rb
@@ -105,7 +105,7 @@ class Chef
           # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz,
           # which is not what you wanted.
           if uri.to_s =~ /gz$/
-            Chef::Log.debug("turning gzip compression off due to filename ending in gz")
+            Chef::Log.debug("Turning gzip compression off due to filename ending in gz")
             opts[:disable_gzip] = true
           end
           opts
diff --git a/lib/chef/provider/remote_file/local_file.rb b/lib/chef/provider/remote_file/local_file.rb
index e78311f..026206b 100644
--- a/lib/chef/provider/remote_file/local_file.rb
+++ b/lib/chef/provider/remote_file/local_file.rb
@@ -32,15 +32,21 @@ class Chef
           @new_resource = new_resource
           @uri = uri
         end
-        
+
         # CHEF-4472: Remove the leading slash from windows paths that we receive from a file:// URI
-        def fix_windows_path(path) 
-          path.gsub(/^\/([a-zA-Z]:)/,'\1')  
+        def fix_windows_path(path)
+          path.gsub(/^\/([a-zA-Z]:)/,'\1')
+        end
+
+        def source_path
+          @source_path ||= begin
+            path = URI.unescape(uri.path)
+            Chef::Platform.windows? ? fix_windows_path(path) : path
+          end
         end
 
         # Fetches the file at uri, returning a Tempfile-like File handle
         def fetch
-          source_path = Chef::Platform.windows? ? fix_windows_path(uri.path) : uri.path
           tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
           Chef::Log.debug("#{new_resource} staging #{source_path} to #{tempfile.path}")
           FileUtils.cp(source_path, tempfile.path)
diff --git a/lib/chef/provider/remote_file/local_file.rb b/lib/chef/provider/remote_file/network_file.rb
similarity index 63%
copy from lib/chef/provider/remote_file/local_file.rb
copy to lib/chef/provider/remote_file/network_file.rb
index e78311f..093a388 100644
--- a/lib/chef/provider/remote_file/local_file.rb
+++ b/lib/chef/provider/remote_file/network_file.rb
@@ -23,27 +23,21 @@ require 'chef/provider/remote_file'
 class Chef
   class Provider
     class RemoteFile
-      class LocalFile
+      class NetworkFile
 
-        attr_reader :uri
         attr_reader :new_resource
 
-        def initialize(uri, new_resource, current_resource)
+        def initialize(source, new_resource, current_resource)
           @new_resource = new_resource
-          @uri = uri
-        end
-        
-        # CHEF-4472: Remove the leading slash from windows paths that we receive from a file:// URI
-        def fix_windows_path(path) 
-          path.gsub(/^\/([a-zA-Z]:)/,'\1')  
+          @source = source
         end
 
-        # Fetches the file at uri, returning a Tempfile-like File handle
+        # Fetches the file on a network share, returning a Tempfile-like File handle
+        # windows only
         def fetch
-          source_path = Chef::Platform.windows? ? fix_windows_path(uri.path) : uri.path
           tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
-          Chef::Log.debug("#{new_resource} staging #{source_path} to #{tempfile.path}")
-          FileUtils.cp(source_path, tempfile.path)
+          Chef::Log.debug("#{new_resource} staging #{@source} to #{tempfile.path}")
+          FileUtils.cp(@source, tempfile.path)
           tempfile.close if tempfile
           tempfile
         end
diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb
index e8b5235..2f2001d 100644
--- a/lib/chef/provider/script.rb
+++ b/lib/chef/provider/script.rb
@@ -27,6 +27,7 @@ class Chef
 
       provides :bash
       provides :csh
+      provides :ksh
       provides :perl
       provides :python
       provides :ruby
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index 75da2dd..d72d135 100644
--- a/lib/chef/provider/service.rb
+++ b/lib/chef/provider/service.rb
@@ -1,6 +1,6 @@
 #
 # Author:: AJ Christensen (<aj at hjksolutions.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +25,10 @@ class Chef
 
       include Chef::Mixin::Command
 
+      def supports
+        @supports ||= new_resource.supports.dup
+      end
+
       def initialize(new_resource, run_context)
         super
         @enabled = nil
@@ -34,23 +38,32 @@ class Chef
         true
       end
 
-     def load_new_resource_state
-        # If the user didn't specify a change in enabled state,
-        # it will be the same as the old resource
-       if ( @new_resource.enabled.nil? )
-         @new_resource.enabled(@current_resource.enabled)
-       end
-       if ( @new_resource.running.nil? )
-         @new_resource.running(@current_resource.running)
-       end
-     end
+      def load_current_resource
+        supports[:status] = false if supports[:status].nil?
+        supports[:reload] = false if supports[:reload].nil?
+        supports[:restart] = false if supports[:restart].nil?
+      end
+
+      # the new_resource#enabled and #running variables are not user input, but when we
+      # do (e.g.) action_enable we want to set new_resource.enabled so that the comparison
+      # between desired and current state produces the correct change in reporting.
+      # XXX?: the #nil? check below will likely fail if this is a cloned resource or if
+      # we just run multiple actions.
+      def load_new_resource_state
+        if ( @new_resource.enabled.nil? )
+          @new_resource.enabled(@current_resource.enabled)
+        end
+        if ( @new_resource.running.nil? )
+          @new_resource.running(@current_resource.running)
+        end
+      end
 
       def shared_resource_requirements
       end
 
       def define_resource_requirements
        requirements.assert(:reload) do |a|
-         a.assertion { @new_resource.supports[:reload] || @new_resource.reload_command }
+         a.assertion { supports[:reload] || @new_resource.reload_command }
          a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
          # if a service is not declared to support reload, that won't
          # typically change during the course of a run - so no whyrun
@@ -168,6 +181,32 @@ class Chef
         @new_resource.respond_to?(method_name) &&
           !!@new_resource.send(method_name)
       end
+
+      module ServicePriorityInit
+
+        #
+        # Platform-specific versions
+        #
+
+        #
+        # Linux
+        #
+
+        require 'chef/chef_class'
+        require 'chef/provider/service/systemd'
+        require 'chef/provider/service/insserv'
+        require 'chef/provider/service/redhat'
+        require 'chef/provider/service/arch'
+        require 'chef/provider/service/gentoo'
+        require 'chef/provider/service/upstart'
+        require 'chef/provider/service/debian'
+        require 'chef/provider/service/invokercd'
+
+        Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: 'arch'
+        Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: 'gentoo'
+        Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: 'debian'
+        Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w(rhel fedora suse)
+      end
     end
   end
 end
diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb
index 0aef62c..0c95ce2 100644
--- a/lib/chef/provider/service/aix.rb
+++ b/lib/chef/provider/service/aix.rb
@@ -91,15 +91,18 @@ class Chef
 
         protected
         def determine_current_status!
-          Chef::Log.debug "#{@new_resource} using lssrc to check the status "
+          Chef::Log.debug "#{@new_resource} using lssrc to check the status"
           begin
-            services = shell_out!("lssrc -a | grep -w #{@new_resource.service_name}").stdout.split("\n")
-            is_resource_group?(services)
-
-            if services.length == 1 && services[0].split(' ').last == "active"
-              @current_resource.running true
-            else
+            if is_resource_group?
+              # Groups as a whole have no notion of whether they're running
               @current_resource.running false
+            else
+              service = shell_out!("lssrc -s #{@new_resource.service_name}").stdout
+              if service.split(' ').last == 'active'
+                @current_resource.running true
+              else
+                @current_resource.running false
+              end
             end
             Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
             # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
@@ -112,11 +115,9 @@ class Chef
           end
         end
 
-        def is_resource_group? (services)
-          if services.length > 1
-            Chef::Log.debug("#{@new_resource.service_name} is a group")
-            @is_resource_group = true
-          elsif services[0].split(' ')[1] == @new_resource.service_name
+        def is_resource_group?
+          so = shell_out("lssrc -g #{@new_resource.service_name}")
+          if so.exitstatus == 0
             Chef::Log.debug("#{@new_resource.service_name} is a group")
             @is_resource_group = true
           end
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 0150592..7d23e4a 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -22,15 +22,13 @@ class Chef
   class Provider
     class Service
       class Debian < Chef::Provider::Service::Init
+        provides :service, platform_family: 'debian' do |node|
+          Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian)
+        end
+
         UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i
         UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i
 
-        provides :service, platform_family: "debian"
-
-        def self.provides?(node, resource)
-          super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian)
-        end
-
         def self.supports?(resource, action)
           Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
         end
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 9204e3e..78ca0be 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -99,7 +99,7 @@ class Chef
         def restart_service
           if new_resource.restart_command
             super
-          elsif new_resource.supports[:restart]
+          elsif supports[:restart]
             shell_out_with_systems_locale!("#{init_command} fastrestart")
           else
             stop_service
@@ -147,7 +147,7 @@ class Chef
                 # some scripts support multiple instances through symlinks such as openvpn.
                 # We should get the service name from rcvar.
                 Chef::Log.debug("name=\"service\" not found at #{init_command}. falling back to rcvar")
-                sn = shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
+                shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
               else
                 # for why-run mode when the rcd_script is not there yet
                 new_resource.service_name
diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb
index 3dab920..903c55a 100644
--- a/lib/chef/provider/service/gentoo.rb
+++ b/lib/chef/provider/service/gentoo.rb
@@ -1,7 +1,7 @@
 #
 # Author:: Lee Jensen (<ljensen at engineyard.com>)
 # Author:: AJ Christensen (<aj at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,9 +26,9 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
   provides :service, platform_family: "gentoo"
 
   def load_current_resource
+    supports[:status] = true if supports[:status].nil?
+    supports[:restart] = true if supports[:restart].nil?
 
-    @new_resource.supports[:status] = true
-    @new_resource.supports[:restart] = true
     @found_script = false
     super
 
diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb
index 0a219a6..8fe5b02 100644
--- a/lib/chef/provider/service/init.rb
+++ b/lib/chef/provider/service/init.rb
@@ -1,6 +1,6 @@
 #
 # Author:: AJ Christensen (<aj at hjksolutions.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,6 +18,7 @@
 
 require 'chef/provider/service/simple'
 require 'chef/mixin/command'
+require 'chef/platform/service_helpers'
 
 class Chef
   class Provider
@@ -71,7 +72,7 @@ class Chef
         def restart_service
           if @new_resource.restart_command
             super
-          elsif @new_resource.supports[:restart]
+          elsif supports[:restart]
             shell_out_with_systems_locale!("#{default_init_command} restart")
           else
             stop_service
@@ -83,7 +84,7 @@ class Chef
         def reload_service
           if @new_resource.reload_command
             super
-          elsif @new_resource.supports[:reload]
+          elsif supports[:reload]
             shell_out_with_systems_locale!("#{default_init_command} reload")
           end
         end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 31965a4..dd01f9a 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -24,10 +24,8 @@ class Chef
     class Service
       class Insserv < Chef::Provider::Service::Init
 
-        provides :service, os: "linux"
-
-        def self.provides?(node, resource)
-          super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
+        provides :service, platform_family: %w(debian rhel fedora suse) do |node|
+          Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
         end
 
         def self.supports?(resource, action)
diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb
index 5ff24e0..2b045e0 100644
--- a/lib/chef/provider/service/invokercd.rb
+++ b/lib/chef/provider/service/invokercd.rb
@@ -23,10 +23,8 @@ class Chef
     class Service
       class Invokercd < Chef::Provider::Service::Init
 
-        provides :service, platform_family: "debian"
-
-        def self.provides?(node, resource)
-          super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
+        provides :service, platform_family: 'debian', override: true do |node|
+          Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
         end
 
         def self.supports?(resource, action)
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 7cfe57a..0a8fca4 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -28,8 +28,8 @@ class Chef
     class Service
       class Macosx < Chef::Provider::Service::Simple
 
-        provides :service, os: "darwin"
         provides :macosx_service, os: "darwin"
+        provides :service, os: "darwin"
 
         def self.gather_plist_dirs
           locations = %w{/Library/LaunchAgents
@@ -42,6 +42,10 @@ class Chef
 
         PLIST_DIRS = gather_plist_dirs
 
+        def this_version_or_newer?(this_version)
+          Gem::Version.new(node['platform_version']) >= Gem::Version.new(this_version)
+        end
+
         def load_current_resource
           @current_resource = Chef::Resource::MacosxService.new(@new_resource.name)
           @current_resource.service_name(@new_resource.service_name)
@@ -56,7 +60,7 @@ class Chef
             @console_user = Etc.getlogin
             Chef::Log.debug("#{new_resource} console_user: '#{@console_user}'")
             cmd = "su "
-            param = !node['platform_version'].include?('10.10') ? '-l ' : ''
+            param = this_version_or_newer?('10.10') ? '' : '-l '
             @base_user_cmd = cmd + param + "#{@console_user} -c"
             # Default LauchAgent session should be Aqua
             @session_type = 'Aqua' if @session_type.nil?
diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb
index d509ee1..36c9e81 100644
--- a/lib/chef/provider/service/openbsd.rb
+++ b/lib/chef/provider/service/openbsd.rb
@@ -26,7 +26,7 @@ class Chef
     class Service
       class Openbsd < Chef::Provider::Service::Init
 
-        provides :service, os: [ "openbsd" ]
+        provides :service, os: "openbsd"
 
         include Chef::Mixin::ShellOut
 
@@ -40,11 +40,12 @@ class Chef
           @rc_conf = ::File.read(RC_CONF_PATH) rescue ''
           @rc_conf_local = ::File.read(RC_CONF_LOCAL_PATH) rescue ''
           @init_command = ::File.exist?(rcd_script_path) ? rcd_script_path : nil
-          new_resource.supports[:status] = true
           new_resource.status_command("#{default_init_command} check")
         end
 
         def load_current_resource
+          supports[:status] = true if supports[:status].nil?
+
           @current_resource = Chef::Resource::Service.new(new_resource.name)
           current_resource.service_name(new_resource.service_name)
 
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 8509531..3ad11a7 100644
--- a/lib/chef/provider/service/redhat.rb
+++ b/lib/chef/provider/service/redhat.rb
@@ -1,6 +1,6 @@
 #
 # Author:: AJ Christensen (<aj at hjksolutions.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,24 +23,32 @@ class Chef
     class Service
       class Redhat < Chef::Provider::Service::Init
 
-        CHKCONFIG_ON = /\d:on/
-        CHKCONFIG_MISSING = /No such/
-
-        provides :service, platform_family: [ "rhel", "fedora", "suse" ]
+        # @api private
+        attr_accessor :service_missing
+        # @api private
+        attr_accessor :current_run_levels
 
-        def self.provides?(node, resource)
-          super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
+        provides :service, platform_family: %w(rhel fedora suse) do |node|
+          Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
         end
 
+        CHKCONFIG_ON = /\d:on/
+        CHKCONFIG_MISSING = /No such/
+
         def self.supports?(resource, action)
           Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
         end
 
         def initialize(new_resource, run_context)
           super
-          @init_command = "/sbin/service #{@new_resource.service_name}"
-          @new_resource.supports[:status] = true
+          @init_command = "/sbin/service #{new_resource.service_name}"
           @service_missing = false
+          @current_run_levels = []
+        end
+
+        # @api private
+        def run_levels
+          new_resource.run_levels
         end
 
         def define_resource_requirements
@@ -49,34 +57,62 @@ class Chef
           requirements.assert(:all_actions) do |a|
             chkconfig_file = "/sbin/chkconfig"
             a.assertion { ::File.exists? chkconfig_file  }
-            a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} does not exist!"
+            a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} dbleoes not exist!"
           end
 
           requirements.assert(:start, :enable, :reload, :restart) do |a|
-            a.assertion { !@service_missing }
-            a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the init.d script!"
+            a.assertion do
+              custom_command_for_action?(action) || !@service_missing
+            end
+            a.failure_message Chef::Exceptions::Service, "#{new_resource}: No custom command for #{action} specified and unable to locate the init.d script!"
             a.whyrun "Assuming service would be disabled. The init script is not presently installed."
           end
         end
 
         def load_current_resource
+          supports[:status] = true if supports[:status].nil?
+
           super
 
           if ::File.exists?("/sbin/chkconfig")
-            chkconfig = shell_out!("/sbin/chkconfig --list #{@current_resource.service_name}", :returns => [0,1])
-            @current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+            chkconfig = shell_out!("/sbin/chkconfig --list #{current_resource.service_name}", :returns => [0,1])
+            unless run_levels.nil? or run_levels.empty?
+              all_levels_match = true
+              chkconfig.stdout.split(/\s+/)[1..-1].each do |level|
+                index = level.split(':').first
+                status = level.split(':').last
+                if level =~ CHKCONFIG_ON
+                  @current_run_levels << index.to_i
+                  all_levels_match = false unless run_levels.include?(index.to_i)
+                else
+                  all_levels_match = false if run_levels.include?(index.to_i)
+                end
+              end
+              current_resource.enabled(all_levels_match)
+            else
+              current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+            end
             @service_missing = !!(chkconfig.stderr =~ CHKCONFIG_MISSING)
           end
 
-          @current_resource
+          current_resource
+        end
+
+        # @api private
+        def levels
+          (run_levels.nil? or run_levels.empty?) ? "" : "--level #{run_levels.join('')} "
         end
 
         def enable_service()
-          shell_out! "/sbin/chkconfig #{@new_resource.service_name} on"
+          unless run_levels.nil? or run_levels.empty?
+            disable_levels = current_run_levels - run_levels
+            shell_out! "/sbin/chkconfig --level #{disable_levels.join('')} #{new_resource.service_name} off" unless disable_levels.empty?
+          end
+          shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} on"
         end
 
         def disable_service()
-          shell_out! "/sbin/chkconfig #{@new_resource.service_name} off"
+          shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} off"
         end
       end
     end
diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb
index ee403ee..d295513 100644
--- a/lib/chef/provider/service/simple.rb
+++ b/lib/chef/provider/service/simple.rb
@@ -76,7 +76,7 @@ class Chef
           end
 
           requirements.assert(:all_actions) do |a|
-            a.assertion { @new_resource.status_command or @new_resource.supports[:status] or
+            a.assertion { @new_resource.status_command or supports[:status] or
               (!ps_cmd.nil? and !ps_cmd.empty?) }
             a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute"
           end
@@ -127,7 +127,7 @@ class Chef
               nil
             end
 
-          elsif @new_resource.supports[:status]
+          elsif supports[:status]
             Chef::Log.debug("#{@new_resource} supports status, running")
             begin
               if shell_out("#{default_init_command} status").exitstatus == 0
diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb
index eaea6bb..7040503 100644
--- a/lib/chef/provider/service/solaris.rb
+++ b/lib/chef/provider/service/solaris.rb
@@ -30,35 +30,39 @@ class Chef
 
         def initialize(new_resource, run_context=nil)
           super
-          @init_command = "/usr/sbin/svcadm"
-          @status_command = "/bin/svcs -l"
+          @init_command   = "/usr/sbin/svcadm"
+          @status_command = "/bin/svcs"
           @maintenace     = false
         end
 
         def load_current_resource
           @current_resource = Chef::Resource::Service.new(@new_resource.name)
           @current_resource.service_name(@new_resource.service_name)
-          unless ::File.exists? "/bin/svcs"
-            raise Chef::Exceptions::Service, "/bin/svcs does not exist!"
+
+          [@init_command, @status_command].each do |cmd|
+            unless ::File.executable? cmd then
+              raise Chef::Exceptions::Service, "#{cmd} not executable!"
+            end
           end
           @status = service_status.enabled
+
           @current_resource
         end
 
         def enable_service
-          shell_out!("#{default_init_command} clear #{@new_resource.service_name}") if @maintenance
-          shell_out!("#{default_init_command} enable -s #{@new_resource.service_name}")
+          shell_out!(default_init_command, "clear", @new_resource.service_name) if @maintenance
+          shell_out!(default_init_command, "enable", "-s", @new_resource.service_name)
         end
 
         def disable_service
-          shell_out!("#{default_init_command} disable -s #{@new_resource.service_name}")
+          shell_out!(default_init_command, "disable", "-s", @new_resource.service_name)
         end
 
         alias_method :stop_service, :disable_service
         alias_method :start_service, :enable_service
 
         def reload_service
-          shell_out_with_systems_locale!("#{default_init_command} refresh #{@new_resource.service_name}")
+          shell_out!(default_init_command, "refresh", @new_resource.service_name)
         end
 
         def restart_service
@@ -68,16 +72,38 @@ class Chef
         end
 
         def service_status
-          status = shell_out!("#{@status_command} #{@current_resource.service_name}", :returns => [0, 1])
-          status.stdout.each_line do |line|
-            case line
-            when /state\s+online/
-              @current_resource.enabled(true)
-              @current_resource.running(true)
-            when /state\s+maintenance/
-              @maintenance = true
-            end
+          cmd = shell_out!(@status_command, "-l", @current_resource.service_name, :returns => [0, 1])
+          # Example output
+          # $ svcs -l rsyslog
+          # fmri         svc:/application/rsyslog:default
+          # name         rsyslog logging utility
+          # enabled      true
+          # state        online
+          # next_state   none
+          # state_time   April  2, 2015 04:25:19 PM EDT
+          # logfile      /var/svc/log/application-rsyslog:default.log
+          # restarter    svc:/system/svc/restarter:default
+          # contract_id  1115271
+          # dependency   require_all/error svc:/milestone/multi-user:default (online)
+          # $
+
+          # load output into hash
+          status = {}
+          cmd.stdout.each_line do |line|
+            key, value = line.strip.split(/\s+/, 2)
+            status[key] = value
+          end
+
+          # check service state
+          @maintenance = false
+          case status['state']
+          when 'online'
+            @current_resource.enabled(true)
+            @current_resource.running(true)
+          when 'maintenance'
+            @maintenance = true
           end
+
           unless @current_resource.enabled
             @current_resource.enabled(false)
             @current_resource.running(false)
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
index 9085ffd..d41f624 100644
--- a/lib/chef/provider/service/systemd.rb
+++ b/lib/chef/provider/service/systemd.rb
@@ -24,14 +24,12 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
 
   include Chef::Mixin::Which
 
-  provides :service, os: "linux"
+  provides :service, os: "linux" do |node|
+    Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd)
+  end
 
   attr_accessor :status_check_success
 
-  def self.provides?(node, resource)
-    super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd)
-  end
-
   def self.supports?(resource, action)
     Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:systemd)
   end
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 8d4aa41..8d9adaa 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -25,14 +25,13 @@ class Chef
   class Provider
     class Service
       class Upstart < Chef::Provider::Service::Simple
-        UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
 
-        provides :service, os: "linux"
-
-        def self.provides?(node, resource)
-          super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
+        provides :service, platform_family: 'debian', override: true do |node|
+          Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
         end
 
+        UPSTART_STATE_FORMAT = /\S+ \(?(start|stop)?\)? ?[\/ ](\w+)/
+
         def self.supports?(resource, action)
           Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart)
         end
@@ -107,7 +106,7 @@ class Chef
             Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
 
             begin
-              if shell_out!(@new_resource.status_command) == 0
+              if shell_out!(@new_resource.status_command).exitstatus == 0
                 @current_resource.running true
               end
             rescue
@@ -225,10 +224,10 @@ class Chef
           command = "/sbin/status #{@job}"
           status = popen4(command) do |pid, stdin, stdout, stderr|
             stdout.each_line do |line|
-              # rsyslog stop/waiting
               # service goal/state
               # OR
-              # rsyslog (stop) waiting
+              # service (instance) goal/state
+              # OR
               # service (goal) state
               line =~ UPSTART_STATE_FORMAT
               data = Regexp.last_match
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
index ba53f0a..355ffaf 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -25,7 +25,6 @@ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
 end
 
 class Chef::Provider::Service::Windows < Chef::Provider::Service
-
   provides :service, os: "windows"
   provides :windows_service, os: "windows"
 
diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb
index 5f36483..e3e3d51 100644
--- a/lib/chef/provider/subversion.rb
+++ b/lib/chef/provider/subversion.rb
@@ -130,8 +130,8 @@ class Chef
             @new_resource.revision
           else
             command = scm(:info, @new_resource.repository, @new_resource.svn_info_args, authentication, "-r#{@new_resource.revision}")
-            status, svn_info, error_message = output_of_command(command, run_options)
-            handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}")
+            svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0,1])).stdout
+
             extract_revision_info(svn_info)
           end
         end
@@ -142,11 +142,8 @@ class Chef
       def find_current_revision
         return nil unless ::File.exist?(::File.join(@new_resource.destination, ".svn"))
         command = scm(:info)
-        status, svn_info, error_message = output_of_command(command, run_options(:cwd => cwd))
+        svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0,1])).stdout
 
-        unless [0,1].include?(status.exitstatus)
-          handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}")
-        end
         extract_revision_info(svn_info)
       end
 
@@ -180,6 +177,7 @@ class Chef
           attrs
         end
         rev = (repo_attrs['Last Changed Rev'] || repo_attrs['Revision'])
+        rev.strip! if rev
         raise "Could not parse `svn info` data: #{svn_info}" if repo_attrs.empty?
         Chef::Log.debug "#{@new_resource} resolved revision #{@new_resource.revision} to #{rev}"
         rev
@@ -197,12 +195,20 @@ class Chef
       end
 
       def scm(*args)
-        ['svn', *args].compact.join(" ")
+        binary = svn_binary
+        binary = "\"#{binary}\"" if binary =~ /\s/
+        [binary, *args].compact.join(" ")
       end
 
       def target_dir_non_existent_or_empty?
         !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..']
       end
+
+      def svn_binary
+        @new_resource.svn_binary ||
+          (Chef::Platform.windows? ? 'svn.exe' : 'svn')
+      end
+
       def assert_target_directory_valid!
         target_parent_directory = ::File.dirname(@new_resource.destination)
         unless ::File.directory?(target_parent_directory)
diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb
index 7fc680e..693b19a 100644
--- a/lib/chef/provider/template/content.rb
+++ b/lib/chef/provider/template/content.rb
@@ -29,20 +29,30 @@ class Chef
 
         def template_location
           @template_file_cache_location ||= begin
-            template_finder.find(@new_resource.source, :local => @new_resource.local, :cookbook => @new_resource.cookbook)
+            template_finder.find(new_resource.source, :local => new_resource.local, :cookbook => new_resource.cookbook)
           end
         end
 
         private
 
         def file_for_provider
-          context = TemplateContext.new(@new_resource.variables)
-          context[:node] = @run_context.node
+          context = TemplateContext.new(new_resource.variables)
+          context[:node] = run_context.node
           context[:template_finder] = template_finder
-          context._extend_modules(@new_resource.helper_modules)
+
+          # helper variables
+          context[:cookbook_name] = new_resource.cookbook_name unless context.keys.include?(:coookbook_name)
+          context[:recipe_name] = new_resource.recipe_name unless context.keys.include?(:recipe_name)
+          context[:recipe_line_string] = new_resource.source_line unless context.keys.include?(:recipe_line_string)
+          context[:recipe_path] = new_resource.source_line_file unless context.keys.include?(:recipe_path)
+          context[:recipe_line] = new_resource.source_line_number unless context.keys.include?(:recipe_line)
+          context[:template_name] = new_resource.source unless context.keys.include?(:template_name)
+          context[:template_path] = template_location unless context.keys.include?(:template_path)
+
+          context._extend_modules(new_resource.helper_modules)
           output = context.render_template(template_location)
 
-          tempfile = Tempfile.open("chef-rendered-template")
+          tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
           tempfile.binmode
           tempfile.write(output)
           tempfile.close
@@ -51,7 +61,7 @@ class Chef
 
         def template_finder
           @template_finder ||= begin
-            TemplateFinder.new(run_context, @new_resource.cookbook_name, @run_context.node)
+            TemplateFinder.new(run_context, new_resource.cookbook_name, run_context.node)
           end
         end
       end
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index f6ac724..76aefbf 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -23,7 +23,6 @@ require 'etc'
 class Chef
   class Provider
     class User < Chef::Provider
-
       include Chef::Mixin::Command
 
       attr_accessor :user_exists, :locked
@@ -90,7 +89,7 @@ class Chef
       end
 
       def define_resource_requirements
-        requirements.assert(:all_actions) do |a|
+        requirements.assert(:create, :modify, :manage, :lock, :unlock) do |a|
           a.assertion { @group_name_resolved }
           a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}"
           a.whyrun "group name #{@new_resource.gid} does not exist.  This will cause group assignment to fail.  Assuming this group will have been created previously."
@@ -208,7 +207,6 @@ class Chef
       def unlock_user
         raise NotImplementedError
       end
-
     end
   end
 end
diff --git a/lib/chef/provider/user/aix.rb b/lib/chef/provider/user/aix.rb
index af08ab4..a575a41 100644
--- a/lib/chef/provider/user/aix.rb
+++ b/lib/chef/provider/user/aix.rb
@@ -18,9 +18,10 @@ class Chef
   class Provider
     class User
       class Aix < Chef::Provider::User::Useradd
+        provides :user, platform: %w(aix)
 
         UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
-        
+
         def create_user
           super
           add_password
@@ -88,7 +89,7 @@ class Chef
             end
           end
         end
-          
+
       end
     end
   end
diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb
index 0c0c85e..1faf243 100644
--- a/lib/chef/provider/user/dscl.rb
+++ b/lib/chef/provider/user/dscl.rb
@@ -44,6 +44,10 @@ class Chef
       # This provider only supports Mac OSX versions 10.7 and above
       class Dscl < Chef::Provider::User
 
+        attr_accessor :user_info
+        attr_accessor :authentication_authority
+        attr_accessor :password_shadow_conversion_algorithm
+
         provides :user, os: "darwin"
 
         def define_resource_requirements
@@ -56,19 +60,19 @@ class Chef
 
           requirements.assert(:all_actions) do |a|
             a.assertion { ::File.exists?("/usr/bin/dscl") }
-            a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{@new_resource}!")
+            a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{new_resource}!")
           end
 
           requirements.assert(:all_actions) do |a|
             a.assertion { ::File.exists?("/usr/bin/plutil") }
-            a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{@new_resource}!")
+            a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{new_resource}!")
           end
 
           requirements.assert(:create, :modify, :manage) do |a|
             a.assertion do
-              if @new_resource.password && mac_osx_version_greater_than_10_7?
+              if new_resource.password && mac_osx_version_greater_than_10_7?
                 # SALTED-SHA512 password shadow hashes are not supported on 10.8 and above.
-                !salted_sha512?(@new_resource.password)
+                !salted_sha512?(new_resource.password)
               else
                 true
               end
@@ -80,10 +84,10 @@ in 'password', with the associated 'salt' and 'iterations'.")
 
           requirements.assert(:create, :modify, :manage) do |a|
             a.assertion do
-              if @new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(@new_resource.password)
+              if new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(new_resource.password)
                 # salt and iterations should be specified when
                 # SALTED-SHA512-PBKDF2 password shadow hash is given
-                !@new_resource.salt.nil? && !@new_resource.iterations.nil?
+                !new_resource.salt.nil? && !new_resource.iterations.nil?
               else
                 true
               end
@@ -94,9 +98,9 @@ in 'password', with the associated 'salt' and 'iterations'.")
 
           requirements.assert(:create, :modify, :manage) do |a|
             a.assertion do
-              if @new_resource.password && !mac_osx_version_greater_than_10_7?
+              if new_resource.password && !mac_osx_version_greater_than_10_7?
                 # On 10.7 SALTED-SHA512-PBKDF2 is not supported
-                !salted_sha512_pbkdf2?(@new_resource.password)
+                !salted_sha512_pbkdf2?(new_resource.password)
               else
                 true
               end
@@ -109,21 +113,21 @@ user password using shadow hash.")
         end
 
         def load_current_resource
-          @current_resource = Chef::Resource::User.new(@new_resource.username)
-          @current_resource.username(@new_resource.username)
+          @current_resource = Chef::Resource::User.new(new_resource.username)
+          current_resource.username(new_resource.username)
 
           @user_info = read_user_info
-          if @user_info
-            @current_resource.uid(dscl_get(@user_info, :uid))
-            @current_resource.gid(dscl_get(@user_info, :gid))
-            @current_resource.home(dscl_get(@user_info, :home))
-            @current_resource.shell(dscl_get(@user_info, :shell))
-            @current_resource.comment(dscl_get(@user_info, :comment))
-            @authentication_authority = dscl_get(@user_info, :auth_authority)
-
-            if @new_resource.password && dscl_get(@user_info, :password) == "********"
+          if user_info
+            current_resource.uid(dscl_get(user_info, :uid))
+            current_resource.gid(dscl_get(user_info, :gid))
+            current_resource.home(dscl_get(user_info, :home))
+            current_resource.shell(dscl_get(user_info, :shell))
+            current_resource.comment(dscl_get(user_info, :comment))
+            @authentication_authority = dscl_get(user_info, :auth_authority)
+
+            if new_resource.password && dscl_get(user_info, :password) == "********"
               # A password is set. Let's get the password information from shadow file
-              shadow_hash_binary = dscl_get(@user_info, :shadow_hash)
+              shadow_hash_binary = dscl_get(user_info, :shadow_hash)
 
               # Calling shell_out directly since we want to give an input stream
               shadow_hash_xml = convert_binary_plist_to_xml(shadow_hash_binary.string)
@@ -132,26 +136,26 @@ user password using shadow hash.")
               if shadow_hash["SALTED-SHA512"]
                 # Convert the shadow value from Base64 encoding to hex before consuming them
                 @password_shadow_conversion_algorithm = "SALTED-SHA512"
-                @current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack('H*').first)
+                current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack('H*').first)
               elsif shadow_hash["SALTED-SHA512-PBKDF2"]
                 @password_shadow_conversion_algorithm = "SALTED-SHA512-PBKDF2"
                 # Convert the entropy from Base64 encoding to hex before consuming them
-                @current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first)
-                @current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"])
+                current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first)
+                current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"])
                 # Convert the salt from Base64 encoding to hex before consuming them
-                @current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack('H*').first)
+                current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack('H*').first)
               else
                 raise(Chef::Exceptions::User,"Unknown shadow_hash format: #{shadow_hash.keys.join(' ')}")
               end
             end
 
-            convert_group_name if @new_resource.gid
+            convert_group_name if new_resource.gid
           else
             @user_exists = false
-            Chef::Log.debug("#{@new_resource} user does not exist")
+            Chef::Log.debug("#{new_resource} user does not exist")
           end
 
-          @current_resource
+          current_resource
         end
 
         #
@@ -190,15 +194,16 @@ user password using shadow hash.")
         # Create a user using dscl
         #
         def dscl_create_user
-          run_dscl("create /Users/#{@new_resource.username}")
+          run_dscl("create /Users/#{new_resource.username}")
         end
 
         #
         # Saves the specified Chef user `comment` into RealName attribute
-        # of Mac user.
+        # of Mac user. If `comment` is not specified, it takes `username` value.
         #
         def dscl_create_comment
-          run_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
+          comment = new_resource.comment || new_resource.username
+          run_dscl("create /Users/#{new_resource.username} RealName '#{comment}'")
         end
 
         #
@@ -207,13 +212,14 @@ user password using shadow hash.")
         # from 200 if `system` is set, 500 otherwise.
         #
         def dscl_set_uid
-          @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '')
+          # XXX: mutates the new resource
+          new_resource.uid(get_free_uid) if (new_resource.uid.nil? || new_resource.uid == '')
 
-          if uid_used?(@new_resource.uid)
-            raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use")
+          if uid_used?(new_resource.uid)
+            raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{new_resource.uid} is already in use")
           end
 
-          run_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}")
+          run_dscl("create /Users/#{new_resource.username} UniqueID #{new_resource.uid}")
         end
 
         #
@@ -222,7 +228,7 @@ user password using shadow hash.")
         #
         def get_free_uid(search_limit=1000)
           uid = nil
-          base_uid = @new_resource.system ? 200 : 500
+          base_uid = new_resource.system ? 200 : 500
           next_uid_guess = base_uid
           users_uids = run_dscl("list /Users uid")
           while(next_uid_guess < search_limit + base_uid)
@@ -248,7 +254,7 @@ user password using shadow hash.")
             tmap
           end
           if uid_map[uid.to_s]
-            unless uid_map[uid.to_s] == @new_resource.username.to_s
+            unless uid_map[uid.to_s] == new_resource.username.to_s
               return true
             end
           end
@@ -257,18 +263,23 @@ user password using shadow hash.")
 
         #
         # Sets the group id for the user using dscl. Fails if a group doesn't
-        # exist on the system with given group id.
+        # exist on the system with given group id. If `gid` is not specified, it
+        # sets a default Mac user group "staff", with id 20.
         #
         def dscl_set_gid
-          unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/)
+          if new_resource.gid.nil?
+            # XXX: mutates the new resource
+            new_resource.gid(20)
+          elsif !new_resource.gid.to_s.match(/^\d+$/)
             begin
-              possible_gid = run_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last
+              possible_gid = run_dscl("read /Groups/#{new_resource.gid} PrimaryGroupID").split(" ").last
             rescue Chef::Exceptions::DsclCommandFailed => e
-              raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}")
+              raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{new_resource.gid} when creating user #{new_resource.username}")
             end
-            @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
+            # XXX: mutates the new resource
+            new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
           end
-          run_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
+          run_dscl("create /Users/#{new_resource.username} PrimaryGroupID '#{new_resource.gid}'")
         end
 
         #
@@ -276,15 +287,15 @@ user password using shadow hash.")
         # directory is managed (moved / created) for the user.
         #
         def dscl_set_home
-          if @new_resource.home.nil? || @new_resource.home.empty?
-            run_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory")
+          if new_resource.home.nil? || new_resource.home.empty?
+            run_dscl("delete /Users/#{new_resource.username} NFSHomeDirectory")
             return
           end
 
-          if @new_resource.supports[:manage_home]
+          if new_resource.supports[:manage_home]
             validate_home_dir_specification!
 
-            if (@current_resource.home == @new_resource.home) && !new_home_exists?
+            if (current_resource.home == new_resource.home) && !new_home_exists?
               ditto_home
             elsif !current_home_exists? && !new_home_exists?
               ditto_home
@@ -292,49 +303,49 @@ user password using shadow hash.")
               move_home
             end
           end
-          run_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'")
+          run_dscl("create /Users/#{new_resource.username} NFSHomeDirectory '#{new_resource.home}'")
         end
 
         def validate_home_dir_specification!
-          unless @new_resource.home =~ /^\//
-            raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
+          unless new_resource.home =~ /^\//
+            raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{new_resource.username}', home directory: '#{new_resource.home}'")
           end
         end
 
         def current_home_exists?
-          ::File.exist?("#{@current_resource.home}")
+          ::File.exist?("#{current_resource.home}")
         end
 
         def new_home_exists?
-          ::File.exist?("#{@new_resource.home}")
+          ::File.exist?("#{new_resource.home}")
         end
 
         def ditto_home
           skel = "/System/Library/User Template/English.lproj"
           raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel)
-          shell_out! "ditto '#{skel}' '#{@new_resource.home}'"
-          ::FileUtils.chown_R(@new_resource.username, at new_resource.gid.to_s, at new_resource.home)
+          shell_out! "ditto '#{skel}' '#{new_resource.home}'"
+          ::FileUtils.chown_R(new_resource.username,new_resource.gid.to_s,new_resource.home)
         end
 
         def move_home
-          Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}")
+          Chef::Log.debug("#{new_resource} moving #{self} home from #{current_resource.home} to #{new_resource.home}")
 
-          src = @current_resource.home
-          FileUtils.mkdir_p(@new_resource.home)
+          src = current_resource.home
+          FileUtils.mkdir_p(new_resource.home)
           files = ::Dir.glob("#{Chef::Util::PathHelper.escape_glob(src)}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."]
-          ::FileUtils.mv(files, at new_resource.home, :force => true)
+          ::FileUtils.mv(files,new_resource.home, :force => true)
           ::FileUtils.rmdir(src)
-          ::FileUtils.chown_R(@new_resource.username, at new_resource.gid.to_s, at new_resource.home)
+          ::FileUtils.chown_R(new_resource.username,new_resource.gid.to_s,new_resource.home)
         end
 
         #
         # Sets the shell for the user using dscl.
         #
         def dscl_set_shell
-          if @new_resource.shell || ::File.exists?("#{@new_resource.shell}")
-            run_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
+          if new_resource.shell || ::File.exists?("#{new_resource.shell}")
+            run_dscl("create /Users/#{new_resource.username} UserShell '#{new_resource.shell}'")
           else
-            run_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
+            run_dscl("create /Users/#{new_resource.username} UserShell '/usr/bin/false'")
           end
         end
 
@@ -345,7 +356,7 @@ user password using shadow hash.")
         #
         def set_password
           # Return if there is no password to set
-          return if @new_resource.password.nil?
+          return if new_resource.password.nil?
 
           shadow_info = prepare_password_shadow_info
 
@@ -355,7 +366,7 @@ user password using shadow hash.")
             :input => shadow_info.to_plist, :live_stream => shadow_info_binary)
           command.run_command
 
-          if @user_info.nil?
+          if user_info.nil?
             # User is  just created. read_user_info() will read the fresh information
             # for the user with a cache flush. However with experimentation we've seen
             # that dscl cache is not immediately updated after the creation of the user
@@ -365,8 +376,8 @@ user password using shadow hash.")
           end
 
           # Replace the shadow info in user's plist
-          dscl_set(@user_info, :shadow_hash, shadow_info_binary)
-          save_user_info(@user_info)
+          dscl_set(user_info, :shadow_hash, shadow_info_binary)
+          save_user_info(user_info)
         end
 
         #
@@ -379,12 +390,12 @@ user password using shadow hash.")
           iterations = nil
 
           if mac_osx_version_10_7?
-            hash_value = if salted_sha512?(@new_resource.password)
-              @new_resource.password
+            hash_value = if salted_sha512?(new_resource.password)
+              new_resource.password
             else
               # Create a random 4 byte salt
               salt = OpenSSL::Random.random_bytes(4)
-              encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + @new_resource.password)
+              encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + new_resource.password)
               hash_value = salt.unpack('H*').first + encoded_password
             end
 
@@ -392,16 +403,16 @@ user password using shadow hash.")
             shadow_info["SALTED-SHA512"].string = convert_to_binary(hash_value)
             shadow_info
           else
-            if salted_sha512_pbkdf2?(@new_resource.password)
-              entropy = convert_to_binary(@new_resource.password)
-              salt = convert_to_binary(@new_resource.salt)
-              iterations = @new_resource.iterations
+            if salted_sha512_pbkdf2?(new_resource.password)
+              entropy = convert_to_binary(new_resource.password)
+              salt = convert_to_binary(new_resource.salt)
+              iterations = new_resource.iterations
             else
               salt = OpenSSL::Random.random_bytes(32)
-              iterations = @new_resource.iterations # Use the default if not specified by the user
+              iterations = new_resource.iterations # Use the default if not specified by the user
 
               entropy = OpenSSL::PKCS5::pbkdf2_hmac(
-                @new_resource.password,
+                new_resource.password,
                 salt,
                 iterations,
                 128,
@@ -427,43 +438,43 @@ user password using shadow hash.")
         # and deleting home directory if needed.
         #
         def remove_user
-          if @new_resource.supports[:manage_home]
+          if new_resource.supports[:manage_home]
             # Remove home directory
-            FileUtils.rm_rf(@current_resource.home)
+            FileUtils.rm_rf(current_resource.home)
           end
 
           # Remove the user from its groups
           run_dscl("list /Groups").each_line do |group|
             if member_of_group?(group.chomp)
-              run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{@new_resource.username}'")
+              run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{new_resource.username}'")
             end
           end
 
           # Remove user account
-          run_dscl("delete /Users/#{@new_resource.username}")
+          run_dscl("delete /Users/#{new_resource.username}")
         end
 
         #
         # Locks the user.
         #
         def lock_user
-          run_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
+          run_dscl("append /Users/#{new_resource.username} AuthenticationAuthority ';DisabledUser;'")
         end
 
         #
         # Unlocks the user
         #
         def unlock_user
-          auth_string = @authentication_authority.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip
-          run_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
+          auth_string = authentication_authority.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip
+          run_dscl("create /Users/#{new_resource.username} AuthenticationAuthority '#{auth_string}'")
         end
 
         #
         # Returns true if the user is locked, false otherwise.
         #
         def locked?
-          if @authentication_authority
-            !!(@authentication_authority =~ /DisabledUser/ )
+          if authentication_authority
+            !!(authentication_authority =~ /DisabledUser/ )
           else
             false
           end
@@ -485,11 +496,11 @@ user password using shadow hash.")
         # given attribute.
         #
         def diverged?(parameter)
-          parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?)
+          parameter_updated?(parameter) && (not new_resource.send(parameter).nil?)
         end
 
         def parameter_updated?(parameter)
-          not (@new_resource.send(parameter) == @current_resource.send(parameter))
+          not (new_resource.send(parameter) == current_resource.send(parameter))
         end
 
         #
@@ -500,11 +511,11 @@ user password using shadow hash.")
         # type of the password specified.
         #
         def diverged_password?
-          return false if @new_resource.password.nil?
+          return false if new_resource.password.nil?
 
           # Dscl provider supports both plain text passwords and shadow hashes.
           if mac_osx_version_10_7?
-            if salted_sha512?(@new_resource.password)
+            if salted_sha512?(new_resource.password)
               diverged?(:password)
             else
               !salted_sha512_password_match?
@@ -514,14 +525,14 @@ user password using shadow hash.")
             # will be updated when the user logs in. So it's possible that we will have
             # SALTED-SHA512 password in the current_resource. In that case we will force
             # password to be updated.
-            return true if salted_sha512?(@current_resource.password)
+            return true if salted_sha512?(current_resource.password)
 
             # Some system users don't have salts; this can happen if the system is
             # upgraded and the user hasn't logged in yet. In this case, we will force
             # the password to be updated.
-            return true if @current_resource.salt.nil?
+            return true if current_resource.salt.nil?
 
-            if salted_sha512_pbkdf2?(@new_resource.password)
+            if salted_sha512_pbkdf2?(new_resource.password)
               diverged?(:password) || diverged?(:salt) || diverged?(:iterations)
             else
               !salted_sha512_pbkdf2_password_match?
@@ -543,7 +554,7 @@ user password using shadow hash.")
           # GroupMembership: root admin etc
           members = membership_info.split(" ")
           members.shift # Get rid of GroupMembership: string
-          members.include?(@new_resource.username)
+          members.include?(new_resource.username)
         end
 
         #
@@ -577,7 +588,7 @@ user password using shadow hash.")
           shell_out("dscacheutil '-flushcache'")
 
           begin
-            user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
+            user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist"
             user_plist_info = run_plutil("convert xml1 -o - #{user_plist_file}")
             user_info = Plist::parse_xml(user_plist_info)
           rescue Chef::Exceptions::PlistUtilCommandFailed
@@ -591,7 +602,7 @@ user password using shadow hash.")
         # in DSCL_PROPERTY_MAP to the disk.
         #
         def save_user_info(user_info)
-          user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
+          user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist"
           Plist::Emit.save_plist(user_info, user_plist_file)
           run_plutil("convert binary1 #{user_plist_file}")
         end
@@ -673,9 +684,9 @@ user password using shadow hash.")
 
         def salted_sha512_password_match?
           # Salt is included in the first 4 bytes of shadow data
-          salt = @current_resource.password.slice(0,8)
-          shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + @new_resource.password)
-          @current_resource.password == salt + shadow
+          salt = current_resource.password.slice(0,8)
+          shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + new_resource.password)
+          current_resource.password == salt + shadow
         end
 
         def salted_sha512_pbkdf2?(string)
@@ -683,15 +694,15 @@ user password using shadow hash.")
         end
 
         def salted_sha512_pbkdf2_password_match?
-          salt = convert_to_binary(@current_resource.salt)
+          salt = convert_to_binary(current_resource.salt)
 
           OpenSSL::PKCS5::pbkdf2_hmac(
-            @new_resource.password,
+            new_resource.password,
             salt,
-            @current_resource.iterations,
+            current_resource.iterations,
             128,
             OpenSSL::Digest::SHA512.new
-          ).unpack('H*').first == @current_resource.password
+          ).unpack('H*').first == current_resource.password
         end
 
       end
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
index fe71e93..810ffb9 100644
--- a/lib/chef/provider/user/pw.rb
+++ b/lib/chef/provider/user/pw.rb
@@ -22,6 +22,7 @@ class Chef
   class Provider
     class User
       class Pw < Chef::Provider::User
+        provides :user, platform: %w(freebsd)
 
         def load_current_resource
           super
diff --git a/lib/chef/provider/user/solaris.rb b/lib/chef/provider/user/solaris.rb
index d480aca..c16db22 100644
--- a/lib/chef/provider/user/solaris.rb
+++ b/lib/chef/provider/user/solaris.rb
@@ -1,7 +1,9 @@
 #
 # Author:: Stephen Nelson-Smith (<sns at opscode.com>)
 # Author:: Jon Ramsey (<jonathon.ramsey at gmail.com>)
+# Author:: Dave Eddy (<dave at daveeddy.com>)
 # Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Copyright:: Copyright 2015, Dave Eddy
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,6 +24,7 @@ class Chef
   class Provider
     class User
       class Solaris < Chef::Provider::User::Useradd
+        provides :user, platform: %w(omnios solaris2)
         UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
 
         attr_writer :password_file
@@ -41,6 +44,32 @@ class Chef
           super
         end
 
+        def check_lock
+          shadow_line = shell_out!('getent', 'shadow', new_resource.username).stdout.strip rescue nil
+
+          # if the command fails we return nil, this can happen if the user
+          # in question doesn't exist
+          return nil if shadow_line.nil?
+
+          # convert "dave:NP:16507::::::\n" to "NP"
+          fields = shadow_line.split(':')
+
+          # '*LK*...' and 'LK' are both considered locked,
+          # so look for LK at the beginning of the shadow entry
+          # optionally surrounded by '*'
+          @locked = !!fields[1].match(/^\*?LK\*?/)
+
+          @locked
+        end
+
+        def lock_user
+          shell_out!('passwd', '-l', new_resource.username)
+        end
+
+        def unlock_user
+          shell_out!('passwd', '-u', new_resource.username)
+        end
+
       private
 
         def manage_password
@@ -65,9 +94,10 @@ class Chef
           buffer.close
 
           # FIXME: mostly duplicates code with file provider deploying a file
-          mode = ::File.stat(@password_file).mode & 07777
-          uid  = ::File.stat(@password_file).uid
-          gid  = ::File.stat(@password_file).gid
+          s = ::File.stat(@password_file)
+          mode = s.mode & 07777
+          uid  = s.uid
+          gid  = s.gid
 
           FileUtils.chown uid, gid, buffer.path
           FileUtils.chmod mode, buffer.path
diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb
index cc770c0..a1b5b34 100644
--- a/lib/chef/provider/user/useradd.rb
+++ b/lib/chef/provider/user/useradd.rb
@@ -23,6 +23,7 @@ class Chef
   class Provider
     class User
       class Useradd < Chef::Provider::User
+        provides :user
 
         UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]]
 
diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb
index e282a11..76519bb 100644
--- a/lib/chef/provider/user/windows.rb
+++ b/lib/chef/provider/user/windows.rb
@@ -35,6 +35,10 @@ class Chef
         end
 
         def load_current_resource
+          if @new_resource.gid
+            Chef::Log.warn("The 'gid' attribute is not implemented by the Windows platform. Please use the 'group' resource to assign a user to a group.")
+          end          
+
           @current_resource = Chef::Resource::User.new(@new_resource.name)
           @current_resource.username(@new_resource.username)
           user_info = nil
@@ -42,7 +46,6 @@ class Chef
             user_info = @net_user.get_info
 
             @current_resource.uid(user_info[:user_id])
-            @current_resource.gid(user_info[:primary_group_id])
             @current_resource.comment(user_info[:full_name])
             @current_resource.home(user_info[:home_dir])
             @current_resource.shell(user_info[:script_path])
@@ -65,7 +68,7 @@ class Chef
             Chef::Log.debug("#{@new_resource} password has changed")
             return true
           end
-          [ :uid, :gid, :comment, :home, :shell ].any? do |user_attrib|
+          [ :uid, :comment, :home, :shell ].any? do |user_attrib|
             !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib)
           end
         end
@@ -100,7 +103,6 @@ class Chef
           field_list = {
             'comment' => 'full_name',
             'home' => 'home_dir',
-            'gid' => 'primary_group_id',
             'uid' => 'user_id',
             'shell' => 'script_path',
             'password' => 'password'
diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb
index e600bb2..62b49bd 100644
--- a/lib/chef/provider/windows_script.rb
+++ b/lib/chef/provider/windows_script.rb
@@ -23,6 +23,8 @@ class Chef
   class Provider
     class WindowsScript < Chef::Provider::Script
 
+      attr_reader :is_forced_32bit
+
       protected
 
       include Chef::Mixin::WindowsArchitectureHelper
@@ -36,11 +38,7 @@ class Chef
 
         @is_wow64 = wow64_architecture_override_required?(run_context.node, target_architecture)
 
-        # if the user wants to run the script 32 bit && we are on a 64bit windows system && we are running a 64bit ruby ==> fail
-        if ( target_architecture == :i386 ) && node_windows_architecture(run_context.node) == :x86_64 && !is_i386_process_on_x86_64_windows?
-          raise Chef::Exceptions::Win32ArchitectureIncorrect,
-          "Support for the i386 architecture from a 64-bit Ruby runtime is not yet implemented"
-        end
+        @is_forced_32bit = forced_32bit_override_required?(run_context.node, target_architecture)
       end
 
       public
diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb
index 5e88722..82a24fc 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -17,9 +17,33 @@
 #
 
 require 'chef/exceptions'
-require 'chef/platform/provider_priority_map'
+require 'chef/platform/priority_map'
 
 class Chef
+  #
+  # Provider Resolution
+  # ===================
+  #
+  # Provider resolution is the process of taking a Resource object and an
+  # action, and determining the Provider class that should be instantiated to
+  # handle the action.
+  #
+  # If the resource has its `provider` set, that is used.
+  #
+  # Otherwise, we take the lists of Providers that have registered as
+  # providing the DSL through `provides :dsl_name, <filters>` or
+  # `Chef.set_resource_priority_array :dsl_name, <filters>`.  We filter each
+  # list of Providers through:
+  #
+  # 1. The filters it was registered with (such as `os: 'linux'` or
+  #    `platform_family: 'debian'`)
+  # 2. `provides?(node, resource)`
+  # 3. `supports?(resource, action)`
+  #
+  # Anything that passes the filter and returns `true` to provides and supports,
+  # is considered a match.  The first matching Provider in the *most recently
+  # registered list* is selected and returned.
+  #
   class ProviderResolver
 
     attr_reader :node
@@ -32,37 +56,53 @@ class Chef
       @action = action
     end
 
-    # return a deterministically sorted list of Chef::Provider subclasses
-    def providers
-      @providers ||= Chef::Provider.descendants
-    end
-
     def resolve
       maybe_explicit_provider(resource) ||
         maybe_dynamic_provider_resolution(resource, action) ||
         maybe_chef_platform_lookup(resource)
     end
 
-    # this cut looks at if the provider can handle the resource type on the node
+    # Does NOT call provides? on the resource (it is assumed this is being
+    # called *from* provides?).
+    def provided_by?(provider_class)
+      potential_handlers.include?(provider_class)
+    end
+
     def enabled_handlers
-      @enabled_handlers ||=
-        providers.select do |klass|
-          # NB: this is different from resource_resolver which must pass a resource_name
-          # FIXME: deprecate this and normalize on passing resource_name here
-          klass.provides?(node, resource)
-        end.sort {|a,b| a.to_s <=> b.to_s }
+      @enabled_handlers ||= potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource) }
     end
 
-    # this cut looks at if the provider can handle the specific resource and action
+    # TODO deprecate this and allow actions to be passed as a filter to
+    # `provides` so we don't have to have two separate things.
+    # @api private
     def supported_handlers
-      @supported_handlers ||=
-        enabled_handlers.select do |klass|
-          klass.supports?(resource, action)
-        end
+      enabled_handlers.select { |handler| handler.supports?(resource, action) }
     end
 
     private
 
+    def potential_handlers
+      handler_map.list(node, resource.resource_name).uniq
+    end
+
+    # The list of handlers, with any in the priority_map moved to the front
+    def prioritized_handlers
+      @prioritized_handlers ||= begin
+        supported_handlers = self.supported_handlers
+        if supported_handlers.empty?
+          # if none of the providers specifically support the resource, we still need to pick one of the providers that are
+          # enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then.
+          Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway."
+          supported_handlers = enabled_handlers
+        end
+
+        prioritized = priority_map.list(node, resource.resource_name).flatten(1)
+        prioritized &= supported_handlers # Filter the priority map by the actual enabled handlers
+        prioritized |= supported_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set)
+        prioritized
+      end
+    end
+
     # if resource.provider is set, just return one of those objects
     def maybe_explicit_provider(resource)
       return nil unless resource.provider
@@ -71,40 +111,17 @@ class Chef
 
     # try dynamically finding a provider based on querying the providers to see what they support
     def maybe_dynamic_provider_resolution(resource, action)
-      # log this so we know what providers will work for the generic resource on the node (early cut)
-      Chef::Log.debug "providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
-
-      # what providers were excluded by machine state (late cut)
-      Chef::Log.debug "providers that refused resource #{resource} were: #{enabled_handlers - supported_handlers}"
-      Chef::Log.debug "providers that support resource #{resource} include: #{supported_handlers}"
-
-      # if none of the providers specifically support the resource, we still need to pick one of the providers that are
-      # enabled on the node to handle the why-run use case.
-      handlers = supported_handlers.empty? ? enabled_handlers : supported_handlers
-      Chef::Log.debug "no providers supported the resource, falling back to enabled handlers" if supported_handlers.empty?
-
-      if handlers.count >= 2
-        # this magic stack ranks the providers by where they appear in the provider_priority_map, it is mostly used
-        # to pick amongst N different ways to start init scripts on different debian/ubuntu systems.
-        priority_list = [ get_priority_array(node, resource.resource_name) ].flatten.compact
-        handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i }
-        if priority_list.index(handlers.first).nil?
-          # if we had more than one and we picked one with a precidence of infinity that means that the resource_priority_map
-          # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity.
-          Chef::Log.warn "Ambiguous provider precedence: #{handlers}, please use Chef.set_provider_priority_array to provide determinism"
-        end
-        handlers = [ handlers.first ]
-      end
-
-      Chef::Log.debug "providers that survived replacement include: #{handlers}"
-
-      raise Chef::Exceptions::AmbiguousProviderResolution.new(resource, handlers) if handlers.count >= 2
+      Chef::Log.debug "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
 
-      Chef::Log.debug "dynamic provider resolver FAILED to resolve a provider" if handlers.empty?
+      handler = prioritized_handlers.first
 
-      return nil if handlers.empty?
+      if handler
+        Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}"
+      else
+        Chef::Log.debug "Dynamic provider resolver FAILED to resolve a provider for action #{action} on resource #{resource}"
+      end
 
-      handlers[0]
+      handler
     end
 
     # try the old static lookup of providers by platform
@@ -112,13 +129,42 @@ class Chef
       Chef::Platform.find_provider_for_node(node, resource)
     end
 
-    # dep injection hooks
-    def get_priority_array(node, resource_name)
-      provider_priority_map.get_priority_array(node, resource_name)
+    def priority_map
+      Chef.provider_priority_map
+    end
+
+    def handler_map
+      Chef.provider_handler_map
     end
 
-    def provider_priority_map
-      Chef::Platform::ProviderPriorityMap.instance
+    def overrode_provides?(handler)
+      handler.method(:provides?).owner != Chef::Provider.method(:provides?).owner
+    end
+
+    module Deprecated
+      # return a deterministically sorted list of Chef::Provider subclasses
+      def providers
+        @providers ||= Chef::Provider.descendants
+      end
+
+      def enabled_handlers
+        @enabled_handlers ||= begin
+          handlers = super
+          if handlers.empty?
+            # Look through all providers, and find ones that return true to provides.
+            # Don't bother with ones that don't override provides?, since they
+            # would have been in enabled_handlers already if that were so. (It's a
+            # perf concern otherwise.)
+            handlers = providers.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource) }
+            handlers.each do |handler|
+              Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!")
+              Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+            end
+          end
+          handlers
+        end
+      end
     end
+    prepend Deprecated
   end
 end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index a5f5386..18500d4 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -122,6 +122,7 @@ require 'chef/provider/deploy/timestamped'
 require 'chef/provider/remote_file/ftp'
 require 'chef/provider/remote_file/http'
 require 'chef/provider/remote_file/local_file'
+require 'chef/provider/remote_file/network_file'
 require 'chef/provider/remote_file/fetcher'
 
 require "chef/provider/lwrp_base"
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index b4d37c2..0f9bf26 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -36,14 +36,7 @@ class Chef
   # A Recipe object is the context in which Chef recipes are evaluated.
   class Recipe
 
-    include Chef::DSL::DataQuery
-    include Chef::DSL::PlatformIntrospection
-    include Chef::DSL::IncludeRecipe
-    include Chef::DSL::Recipe
-    include Chef::DSL::RegistryHelper
-    include Chef::DSL::RebootPending
-    include Chef::DSL::Audit
-    include Chef::DSL::Powershell
+    include Chef::DSL::Recipe::FullDSL
 
     include Chef::Mixin::FromFile
     include Chef::Mixin::Deprecation
@@ -104,10 +97,8 @@ class Chef
     # true<TrueClass>:: If all the parameters are present
     # false<FalseClass>:: If any of the parameters are missing
     def tagged?(*tags)
-      return false if run_context.node[:tags].nil?
-
       tags.each do |tag|
-        return false unless run_context.node[:tags].include?(tag)
+        return false unless run_context.node.tags.include?(tag)
       end
       true
     end
@@ -118,10 +109,10 @@ class Chef
     # tags<Array>:: A list of tags
     #
     # === Returns
-    # tags<Array>:: The current list of run_context.node[:tags]
+    # tags<Array>:: The current list of run_context.node.tags
     def untag(*tags)
       tags.each do |tag|
-        run_context.node.normal[:tags].delete(tag)
+        run_context.node.tags.delete(tag)
       end
     end
 
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index d934ec8..67cf4cd 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -1,7 +1,8 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Christopher Walters (<cw at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Author:: John Keiser (<jkeiser at chef.io)
+# Copyright:: Copyright (c) 2008-2015 Chef, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,23 +18,32 @@
 # limitations under the License.
 #
 
-require 'chef/mixin/params_validate'
+require 'chef/exceptions'
 require 'chef/dsl/platform_introspection'
 require 'chef/dsl/data_query'
 require 'chef/dsl/registry_helper'
 require 'chef/dsl/reboot_pending'
+require 'chef/dsl/resources'
 require 'chef/mixin/convert_to_class_name'
 require 'chef/guard_interpreter/resource_guard_interpreter'
 require 'chef/resource/conditional'
 require 'chef/resource/conditional_action_not_nothing'
+require 'chef/resource/action_class'
 require 'chef/resource_collection'
 require 'chef/node_map'
 require 'chef/node'
 require 'chef/platform'
 require 'chef/resource/resource_notification'
+require 'chef/provider_resolver'
+require 'chef/resource_resolver'
+require 'chef/provider'
+require 'set'
 
 require 'chef/mixin/deprecation'
+require 'chef/mixin/properties'
 require 'chef/mixin/provides'
+require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
 
 class Chef
   class Resource
@@ -48,6 +58,38 @@ class Chef
     include Chef::DSL::RebootPending
     extend Chef::Mixin::Provides
 
+    # This lets user code do things like `not_if { shell_out!("command") }`
+    include Chef::Mixin::ShellOut
+    include Chef::Mixin::PowershellOut
+
+    # Bring in `property` and `property_type`
+    include Chef::Mixin::Properties
+
+    #
+    # The name of this particular resource.
+    #
+    # This special resource attribute is set automatically from the declaration
+    # of the resource, e.g.
+    #
+    #   execute 'Vitruvius' do
+    #     command 'ls'
+    #   end
+    #
+    # Will set the name to "Vitruvius".
+    #
+    # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
+    #
+    # This is also used for resource notifications and subscribes in the same manner.
+    #
+    # This will coerce any object into a string via #to_s.  Arrays are a special case
+    # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
+    # awkward `package[["foo", "bar"]]` that #to_s would produce.
+    #
+    # @param name [Object] The name to set, typically a String or Array
+    # @return [String] The name of this Resource.
+    #
+    property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(', ') : v.to_s }, desired_state: false
+
     #
     # The node the current Chef run is using.
     #
@@ -79,7 +121,6 @@ class Chef
       run_context.resource_collection.find(*args)
     end
 
-
     #
     # Resource User Interface (for users)
     #
@@ -92,14 +133,14 @@ class Chef
     # @param run_context The context of the Chef run. Corresponds to #run_context.
     #
     def initialize(name, run_context=nil)
-      name(name)
+      name(name) unless name.nil?
       @run_context = run_context
       @noop = nil
       @before = nil
       @params = Hash.new
       @provider = nil
-      @allowed_actions = [ :nothing ]
-      @action = :nothing
+      @allowed_actions = self.class.allowed_actions.to_a
+      @action = self.class.default_action
       @updated = false
       @updated_by_last_action = false
       @supports = {}
@@ -121,40 +162,6 @@ class Chef
     end
 
     #
-    # The name of this particular resource.
-    #
-    # This special resource attribute is set automatically from the declaration
-    # of the resource, e.g.
-    #
-    #   execute 'Vitruvius' do
-    #     command 'ls'
-    #   end
-    #
-    # Will set the name to "Vitruvius".
-    #
-    # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
-    #
-    # This is also used for resource notifications and subscribes in the same manner.
-    #
-    # This will coerce any object into a string via #to_s.  Arrays are a special case
-    # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
-    # awkward `package[["foo", "bar"]]` that #to_s would produce.
-    #
-    # @param name [Object] The name to set, typically a String or Array
-    # @return [String] The name of this Resource.
-    #
-    def name(name=nil)
-      if !name.nil?
-        if name.is_a?(Array)
-          @name = name.join(', ')
-        else
-          @name = name.to_s
-        end
-      end
-      @name
-    end
-
-    #
     # The action or actions that will be taken when this resource is run.
     #
     # @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`)
@@ -162,26 +169,27 @@ class Chef
     #
     def action(arg=nil)
       if arg
-        action_list = arg.kind_of?(Array) ? arg : [ arg ]
-        action_list = action_list.collect { |a| a.to_sym }
-        action_list.each do |action|
+        arg = Array(arg).map(&:to_sym)
+        arg.each do |action|
           validate(
             { action: action },
-            { action: { kind_of: Symbol, equal_to: @allowed_actions } }
+            { action: { kind_of: Symbol, equal_to: allowed_actions } }
           )
         end
-        @action = action_list
+        @action = arg
       else
         @action
       end
     end
 
+    # Alias for normal assigment syntax.
+    alias_method :action=, :action
+
     #
     # Sets up a notification that will run a particular action on another resource
     # if and when *this* resource is updated by an action.
     #
-    # If the action does nothing--does not update this resource, the
-    # notification never triggers.)
+    # If the action does not update this resource, the notification never triggers.
     #
     # Only one resource may be specified per notification.
     #
@@ -201,6 +209,8 @@ class Chef
     #     actions have been run.  This is the default.
     #   - `immediate`, `immediately`: Will run the action on the other resource
     #     immediately (before any other action is run).
+    #   - `before`: Will run the action on the other resource
+    #     immediately *before* the action is actually run.
     #
     # @example Resource by string
     #   file '/foo.txt' do
@@ -243,9 +253,11 @@ class Chef
           notifies_delayed(action, resource)
         when 'immediate', 'immediately'
           notifies_immediately(action, resource)
+        when 'before'
+          notifies_before(action, resource)
         else
           raise ArgumentError,  "invalid timing: #{timing} for notifies(#{action}, #{resources.inspect}, #{timing}) resource #{self} "\
-          "Valid timings are: :delayed, :immediate, :immediately"
+          "Valid timings are: :delayed, :immediate, :immediately, :before"
         end
       end
 
@@ -269,6 +281,8 @@ class Chef
     #     actions have been run.  This is the default.
     #   - `immediate`, `immediately`: The action will run immediately following
     #     the other resource being updated.
+    #   - `before`: The action will run immediately before the
+    #     other resource is updated.
     #
     # @example Resources by string
     #   file '/foo.txt' do
@@ -465,27 +479,49 @@ class Chef
     #
     # Get the value of the state attributes in this resource as a hash.
     #
+    # Does not include properties that are not set (unless they are identity
+    # properties).
+    #
     # @return [Hash{Symbol => Object}] A Hash of attribute => value for the
     #   Resource class's `state_attrs`.
-    def state
-      self.class.state_attrs.inject({}) do |state_attrs, attr_name|
-        state_attrs[attr_name] = send(attr_name)
-        state_attrs
+    #
+    def state_for_resource_reporter
+      state = {}
+      state_properties = self.class.state_properties
+      state_properties.each do |property|
+        if property.identity? || property.is_set?(self)
+          state[property.name] = send(property.name)
+        end
       end
+      state
     end
 
     #
-    # The value of the identity attribute, if declared. Falls back to #name if
-    # no identity attribute is declared.
+    # Since there are collisions with LWRP parameters named 'state' this
+    # method is not used by the resource_reporter and is most likely unused.
+    # It certainly cannot be relied upon and cannot be fixed.
     #
-    # @return The value of the identity attribute.
+    # @deprecated
+    #
+    alias_method :state, :state_for_resource_reporter
+
+    #
+    # The value of the identity of this resource.
+    #
+    # - If there are no identity properties on the resource, `name` is returned.
+    # - If there is exactly one identity property on the resource, it is returned.
+    # - If there are more than one, they are returned in a hash.
+    #
+    # @return [Object,Hash<Symbol,Object>] The identity of this resource.
     #
     def identity
-      if identity_attr = self.class.identity_attr
-        send(identity_attr)
-      else
-        name
+      result = {}
+      identity_properties = self.class.identity_properties
+      identity_properties.each do |property|
+        result[property.name] = send(property.name)
       end
+      return result.values.first if identity_properties.size == 1
+      result
     end
 
     #
@@ -507,9 +543,7 @@ class Chef
     #
     # Equivalent to #ignore_failure.
     #
-    def epic_fail(arg=nil)
-      ignore_failure(arg)
-    end
+    alias :epic_fail :ignore_failure
 
     #
     # Make this resource into an exact (shallow) copy of the other resource.
@@ -574,12 +608,26 @@ class Chef
           events.resource_failed(self, action, e)
           raise customize_exception(e)
         end
-      ensure
-        @elapsed_time = Time.now - start_time
-        # Reporting endpoint doesn't accept a negative resource duration so set it to 0.
-        # A negative value can occur when a resource changes the system time backwards
-        @elapsed_time = 0 if @elapsed_time < 0
-        events.resource_completed(self)
+      end
+    ensure
+      @elapsed_time = Time.now - start_time
+      # Reporting endpoint doesn't accept a negative resource duration so set it to 0.
+      # A negative value can occur when a resource changes the system time backwards
+      @elapsed_time = 0 if @elapsed_time < 0
+      events.resource_completed(self)
+    end
+
+    #
+    # If we are currently initializing the resource, this will be true.
+    #
+    # Do NOT use this. It may be removed. It is for internal purposes only.
+    # @api private
+    attr_reader :resource_initializing
+    def resource_initializing=(value)
+      if value
+        @resource_initializing = true
+      else
+        remove_instance_variable(:@resource_initializing)
       end
     end
 
@@ -588,14 +636,14 @@ class Chef
     #
 
     def to_s
-      "#{@resource_name}[#{@name}]"
+      "#{resource_name}[#{name}]"
     end
 
     def to_text
       return "suppressed sensitive resource output" if sensitive
       ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS
       text = "# Declared in #{@source_line}\n\n"
-      text << self.class.dsl_name + "(\"#{name}\") do\n"
+      text << "#{resource_name}(\"#{name}\") do\n"
       ivars.each do |ivar|
         if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?)
           value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect
@@ -637,13 +685,18 @@ class Chef
     end
 
     def to_hash
+      # Grab all current state, then any other ivars (backcompat)
+      result = {}
+      self.class.state_properties.each do |p|
+        result[p.name] = p.get(self)
+      end
       safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS
-      instance_vars = Hash.new
       safe_ivars.each do |iv|
         key = iv.to_s.sub(/^@/,'').to_sym
-        instance_vars[key] = instance_variable_get(iv)
+        next if result.has_key?(key)
+        result[key] = instance_variable_get(iv)
       end
-      instance_vars
+      result
     end
 
     def self.json_create(o)
@@ -658,72 +711,112 @@ class Chef
     # Resource Definition Interface (for resource developers)
     #
 
-    include Chef::Mixin::ParamsValidate
     include Chef::Mixin::Deprecation
 
     #
     # The provider class for this resource.
     #
+    # If `action :x do ... end` has been declared on this resource or its
+    # superclasses, this will return the `action_class`.
+    #
     # If this is not set, `provider_for_action` will dynamically determine the
     # provider.
     #
     # @param arg [String, Symbol, Class] Sets the provider class for this resource.
     #   If passed a String or Symbol, e.g. `:file` or `"file"`, looks up the
     #   provider based on the name.
+    #
     # @return The provider class for this resource.
     #
+    # @see Chef::Resource.action_class
+    #
     def provider(arg=nil)
       klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
         lookup_provider_constant(arg)
       else
         arg
       end
-      set_or_return(:provider, klass, kind_of: [ Class ])
+      set_or_return(:provider, klass, kind_of: [ Class ]) ||
+        self.class.action_class
     end
     def provider=(arg)
       provider(arg)
     end
 
-    # Set or return the list of "state attributes" implemented by the Resource
-    # subclass. State attributes are attributes that describe the desired state
-    # of the system, such as file permissions or ownership. In general, state
-    # attributes are attributes that could be populated by examining the state
-    # of the system (e.g., File.stat can tell you the permissions on an
-    # existing file). Contrarily, attributes that are not "state attributes"
-    # usually modify the way Chef itself behaves, for example by providing
-    # additional options for a package manager to use when installing a
-    # package.
-    #
-    # This list is used by the Chef client auditing system to extract
-    # information from resources to describe changes made to the system.
-    def self.state_attrs(*attr_names)
-      @state_attrs ||= []
-      @state_attrs = attr_names unless attr_names.empty?
-
-      # Return *all* state_attrs that this class has, including inherited ones
-      if superclass.respond_to?(:state_attrs)
-        superclass.state_attrs + @state_attrs
-      else
-        @state_attrs
-      end
+    #
+    # Set or return the list of "state properties" implemented by the Resource
+    # subclass.
+    #
+    # Equivalent to calling #state_properties and getting `state_properties.keys`.
+    #
+    # @deprecated Use state_properties.keys instead. Note that when you declare
+    #   properties with `property`: properties are added to state_properties by
+    #   default, and can be turned off with `desired_state: false`
+    #
+    #   ```ruby
+    #   property :x # part of desired state
+    #   property :y, desired_state: false # not part of desired state
+    #   ```
+    #
+    # @param names [Array<Symbol>] A list of property names to set as desired
+    #   state.
+    #
+    # @return [Array<Symbol>] All property names with desired state.
+    #
+    def self.state_attrs(*names)
+      state_properties(*names).map { |property| property.name }
     end
 
-    # Set or return the "identity attribute" for this resource class. This is
-    # generally going to be the "name attribute" for this resource. In other
-    # words, the resource type plus this attribute uniquely identify a given
-    # bit of state that chef manages. For a File resource, this would be the
-    # path, for a package resource, it will be the package name. This will show
-    # up in chef-client's audit records as a searchable field.
-    def self.identity_attr(attr_name=nil)
-      @identity_attr ||= nil
-      @identity_attr = attr_name if attr_name
-
-      # If this class doesn't have an identity attr, we'll defer to the superclass:
-      if @identity_attr || !superclass.respond_to?(:identity_attr)
-        @identity_attr
-      else
-        superclass.identity_attr
+    #
+    # Set the identity of this resource to a particular property.
+    #
+    # This drives #identity, which returns data that uniquely refers to a given
+    # resource on the given node (in such a way that it can be correlated
+    # across Chef runs).
+    #
+    # This method is unnecessary when declaring properties with `property`;
+    # properties can be added to identity during declaration with
+    # `identity: true`.
+    #
+    # ```ruby
+    # property :x, identity: true # part of identity
+    # property :y # not part of identity
+    # ```
+    #
+    # @param name [Symbol] A list of property names to set as the identity.
+    #
+    # @return [Symbol] The identity property if there is only one; or `nil` if
+    #   there are more than one.
+    #
+    # @raise [ArgumentError] If no arguments are passed and the resource has
+    #   more than one identity property.
+    #
+    def self.identity_property(name=nil)
+      result = identity_properties(*Array(name))
+      if result.size > 1
+        raise Chef::Exceptions::MultipleIdentityError, "identity_property cannot be called on an object with more than one identity property (#{result.map { |r| r.name }.join(", ")})."
       end
+      result.first
+    end
+
+    #
+    # Set a property as the "identity attribute" for this resource.
+    #
+    # Identical to calling #identity_property.first.key.
+    #
+    # @param name [Symbol] The name of the property to set.
+    #
+    # @return [Symbol]
+    #
+    # @deprecated `identity_property` should be used instead.
+    #
+    # @raise [ArgumentError] If no arguments are passed and the resource has
+    #   more than one identity property.
+    #
+    def self.identity_attr(name=nil)
+      property = identity_property(name)
+      return nil if !property
+      property.name
     end
 
     #
@@ -749,6 +842,12 @@ class Chef
     #   have.
     #
     attr_accessor :allowed_actions
+    def allowed_actions(value=NOT_PASSED)
+      if value != NOT_PASSED
+        self.allowed_actions = value
+      end
+      @allowed_actions
+    end
 
     #
     # Whether or not this resource was updated during an action.  If multiple
@@ -807,19 +906,15 @@ class Chef
     end
 
     #
-    # The DSL name of this resource (e.g. `package` or `yum_package`)
+    # The display name of this resource type, for printing purposes.
     #
-    # @return [String] The DSL name of this resource.
-    def self.dsl_name
-      convert_to_snake_case(name, 'Chef::Resource')
-    end
-
-    #
-    # The name of this resource (e.g. `file`)
+    # Will be used to print out the resource in messages, e.g. resource_name[name]
     #
-    # @return [String] The name of this resource.
+    # @return [Symbol] The name of this resource type (e.g. `:execute`).
     #
-    attr_reader :resource_name
+    def resource_name
+      @resource_name || self.class.resource_name
+    end
 
     #
     # Sets a list of capabilities of the real resource.  For example, `:remount`
@@ -852,6 +947,73 @@ class Chef
     end
 
     #
+    # The DSL name of this resource (e.g. `package` or `yum_package`)
+    #
+    # @return [String] The DSL name of this resource.
+    #
+    # @deprecated Use resource_name instead.
+    #
+    def self.dsl_name
+      Chef.log_deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13.  Use resource_name instead."
+      if name
+        name = self.name.split('::')[-1]
+        convert_to_snake_case(name)
+      end
+    end
+
+    #
+    # The display name of this resource type, for printing purposes.
+    #
+    # This also automatically calls "provides" to provide DSL with the given
+    # name.
+    #
+    # resource_name defaults to your class name.
+    #
+    # Call `resource_name nil` to remove the resource name (and any
+    # corresponding DSL).
+    #
+    # @param value [Symbol] The desired name of this resource type (e.g.
+    #   `execute`), or `nil` if this class is abstract and has no resource_name.
+    #
+    # @return [Symbol] The name of this resource type (e.g. `:execute`).
+    #
+    def self.resource_name(name=NOT_PASSED)
+      # Setter
+      if name != NOT_PASSED
+        remove_canonical_dsl
+
+        # Set the resource_name and call provides
+        if name
+          name = name.to_sym
+          # If our class is not already providing this name, provide it.
+          if !Chef::ResourceResolver.includes_handler?(name, self)
+            provides name, canonical: true
+          end
+          @resource_name = name
+        else
+          @resource_name = nil
+        end
+      end
+      @resource_name
+    end
+    def self.resource_name=(name)
+      resource_name(name)
+    end
+
+    #
+    # Use the class name as the resource name.
+    #
+    # Munges the last part of the class name from camel case to snake case,
+    # and sets the resource_name to that:
+    #
+    # A::B::BlahDBlah -> blah_d_blah
+    #
+    def self.use_automatic_resource_name
+      automatic_name = convert_to_snake_case(self.name.split('::')[-1])
+      resource_name automatic_name
+    end
+
+    #
     # The module where Chef should look for providers for this resource.
     # The provider for `MyResource` will be looked up using
     # `provider_base::MyResource`.  Defaults to `Chef::Provider`.
@@ -865,11 +1027,185 @@ class Chef
     #     # ...other stuff
     #   end
     #
+    # @deprecated Use `provides` on the provider, or `provider` on the resource, instead.
+    #
     def self.provider_base(arg=nil)
-      @provider_base ||= arg
-      @provider_base ||= Chef::Provider
+      if arg
+        Chef.log_deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.")
+      end
+      @provider_base ||= arg || Chef::Provider
+    end
+
+    #
+    # The list of allowed actions for the resource.
+    #
+    # @param actions [Array<Symbol>] The list of actions to add to allowed_actions.
+    #
+    # @return [Array<Symbol>] The list of actions, as symbols.
+    #
+    def self.allowed_actions(*actions)
+      @allowed_actions ||=
+        if superclass.respond_to?(:allowed_actions)
+          superclass.allowed_actions.dup
+        else
+          [ :nothing ]
+        end
+      @allowed_actions |= actions.flatten
+    end
+    def self.allowed_actions=(value)
+      @allowed_actions = value.uniq
+    end
+
+    #
+    # The action that will be run if no other action is specified.
+    #
+    # Setting default_action will automatially add the action to
+    # allowed_actions, if it isn't already there.
+    #
+    # Defaults to [:nothing].
+    #
+    # @param action_name [Symbol,Array<Symbol>] The default action (or series
+    #   of actions) to use.
+    #
+    # @return [Array<Symbol>] The default actions for the resource.
+    #
+    def self.default_action(action_name=NOT_PASSED)
+      unless action_name.equal?(NOT_PASSED)
+        @default_action = Array(action_name).map(&:to_sym)
+        self.allowed_actions |= @default_action
+      end
+
+      if @default_action
+        @default_action
+      elsif superclass.respond_to?(:default_action)
+        superclass.default_action
+      else
+        [:nothing]
+      end
+    end
+    def self.default_action=(action_name)
+      default_action action_name
+    end
+
+    #
+    # Define an action on this resource.
+    #
+    # The action is defined as a *recipe* block that will be compiled and then
+    # converged when the action is taken (when Resource is converged).  The recipe
+    # has access to the resource's attributes and methods, as well as the Chef
+    # recipe DSL.
+    #
+    # Resources in the action recipe may notify and subscribe to other resources
+    # within the action recipe, but cannot notify or subscribe to resources
+    # in the main Chef run.
+    #
+    # Resource actions are *inheritable*: if resource A defines `action :create`
+    # and B is a subclass of A, B gets all of A's actions.  Additionally,
+    # resource B can define `action :create` and call `super()` to invoke A's
+    # action code.
+    #
+    # The first action defined (besides `:nothing`) will become the default
+    # action for the resource.
+    #
+    # @param name [Symbol] The action name to define.
+    # @param recipe_block The recipe to run when the action is taken. This block
+    #   takes no parameters, and will be evaluated in a new context containing:
+    #
+    #   - The resource's public and protected methods (including attributes)
+    #   - The Chef Recipe DSL (file, etc.)
+    #   - super() referring to the parent version of the action (if any)
+    #
+    # @return The Action class implementing the action
+    #
+    def self.action(action, &recipe_block)
+      action = action.to_sym
+      declare_action_class
+      action_class.action(action, &recipe_block)
+      self.allowed_actions += [ action ]
+      default_action action if Array(default_action) == [:nothing]
+    end
+
+    #
+    # Define a method to load up this resource's properties with the current
+    # actual values.
+    #
+    # @param load_block The block to load.  Will be run in the context of a newly
+    #   created resource with its identity values filled in.
+    #
+    def self.load_current_value(&load_block)
+      define_method(:load_current_value!, &load_block)
+    end
+
+    #
+    # Call this in `load_current_value` to indicate that the value does not
+    # exist and that `current_resource` should therefore be `nil`.
+    #
+    # @raise Chef::Exceptions::CurrentValueDoesNotExist
+    #
+    def current_value_does_not_exist!
+      raise Chef::Exceptions::CurrentValueDoesNotExist
+    end
+
+    #
+    # Get the current actual value of this resource.
+    #
+    # This does not cache--a new value will be returned each time.
+    #
+    # @return A new copy of the resource, with values filled in from the actual
+    #   current value.
+    #
+    def current_value
+      provider = provider_for_action(Array(action).first)
+      if provider.whyrun_mode? && !provider.whyrun_supported?
+        raise "Cannot retrieve #{self.class.current_resource} in why-run mode: #{provider} does not support why-run"
+      end
+      provider.load_current_resource
+      provider.current_resource
+    end
+
+    #
+    # The action class is an automatic `Provider` created to handle
+    # actions declared by `action :x do ... end`.
+    #
+    # This class will be returned by `resource.provider` if `resource.provider`
+    # is not set. `provider_for_action` will also use this instead of calling
+    # out to `Chef::ProviderResolver`.
+    #
+    # If the user has not declared actions on this class or its superclasses
+    # using `action :x do ... end`, then there is no need for this class and
+    # `action_class` will be `nil`.
+    #
+    # @api private
+    #
+    def self.action_class
+      @action_class ||
+        # If the superclass needed one, then we need one as well.
+        if superclass.respond_to?(:action_class) && superclass.action_class
+          declare_action_class
+        end
     end
 
+    #
+    # Ensure the action class actually gets created. This is called
+    # when the user does `action :x do ... end`.
+    #
+    # If a block is passed, it is run inside the action_class.
+    #
+    # @api private
+    def self.declare_action_class
+      return @action_class if @action_class
+
+      if superclass.respond_to?(:action_class)
+        base_provider = superclass.action_class
+      end
+      base_provider ||= Chef::Provider
+
+      resource_class = self
+      @action_class = Class.new(base_provider) do
+        include ActionClass
+        self.resource_class = resource_class
+      end
+    end
 
     #
     # Internal Resource Interface (for Chef)
@@ -922,6 +1258,9 @@ class Chef
     # resolve_resource_reference on each in turn, causing them to
     # resolve lazy/forward references.
     def resolve_notification_references
+      run_context.before_notifications(self).each { |n|
+        n.resolve_resource_reference(run_context.resource_collection)
+      }
       run_context.immediate_notifications(self).each { |n|
         n.resolve_resource_reference(run_context.resource_collection)
       }
@@ -931,6 +1270,11 @@ class Chef
     end
 
     # Helper for #notifies
+    def notifies_before(action, resource_spec)
+      run_context.notifies_before(Notification.new(resource_spec, action, self))
+    end
+
+    # Helper for #notifies
     def notifies_immediately(action, resource_spec)
       run_context.notifies_immediately(Notification.new(resource_spec, action, self))
     end
@@ -942,13 +1286,39 @@ class Chef
 
     class << self
       # back-compat
-      # NOTE: that we do not support unregistering classes as descendents like
+      # NOTE: that we do not support unregistering classes as descendants like
       # we used to for LWRP unloading because that was horrible and removed in
       # Chef-12.
+      # @deprecated
+      # @api private
       alias :resource_classes :descendants
+      # @deprecated
+      # @api private
       alias :find_subclass_by_name :find_descendants_by_name
     end
 
+    # @deprecated
+    # @api private
+    # We memoize a sorted version of descendants so that resource lookups don't
+    # have to sort all the things, all the time.
+    # This was causing performance issues in test runs, and probably in real
+    # life as well.
+    @@sorted_descendants = nil
+    def self.sorted_descendants
+      @@sorted_descendants ||= descendants.sort_by { |x| x.to_s }
+    end
+    def self.inherited(child)
+      super
+      @@sorted_descendants = nil
+      # set resource_name automatically if it's not set
+      if child.name && !child.resource_name
+        if child.name =~ /^Chef::Resource::(\w+)$/
+          child.resource_name(convert_to_snake_case($1))
+        end
+      end
+    end
+
+
     # If an unknown method is invoked, determine whether the enclosing Provider's
     # lexical scope can fulfill the request. E.g. This happens when the Resource's
     # block invokes new_resource.
@@ -960,6 +1330,32 @@ class Chef
       end
     end
 
+    #
+    # Mark this resource as providing particular DSL.
+    #
+    # Resources have an automatic DSL based on their resource_name, equivalent to
+    # `provides :resource_name` (providing the resource on all OS's).  If you
+    # declare a `provides` with the given resource_name, it *replaces* that
+    # provides (so that you can provide your resource DSL only on certain OS's).
+    #
+    def self.provides(name, **options, &block)
+      name = name.to_sym
+
+      # `provides :resource_name, os: 'linux'`) needs to remove the old
+      # canonical DSL before adding the new one.
+      if @resource_name && name == @resource_name
+        remove_canonical_dsl
+      end
+
+      result = Chef.resource_handler_map.set(name, self, options, &block)
+      Chef::DSL::Resources.add_resource_dsl(name)
+      result
+    end
+
+    def self.provides?(node, resource_name)
+      Chef::ResourceResolver.new(node, resource_name).provided_by?(self)
+    end
+
     # Helper for #notifies
     def validate_resource_spec!(resource_spec)
       run_context.resource_collection.validate_lookup_spec!(resource_spec)
@@ -973,6 +1369,10 @@ class Chef
       "#{declared_type}[#{@name}]"
     end
 
+    def before_notifications
+      run_context.before_notifications(self)
+    end
+
     def immediate_notifications
       run_context.immediate_notifications(self)
     end
@@ -981,16 +1381,31 @@ class Chef
       run_context.delayed_notifications(self)
     end
 
+    def source_line_file
+      if source_line
+        source_line.match(/(.*):(\d+):?.*$/).to_a[1]
+      else
+        nil
+      end
+    end
+
+    def source_line_number
+      if source_line
+        source_line.match(/(.*):(\d+):?.*$/).to_a[2]
+      else
+        nil
+      end
+    end
+
     def defined_at
       # The following regexp should match these two sourceline formats:
       #   /some/path/to/file.rb:80:in `wombat_tears'
       #   C:/some/path/to/file.rb:80 in 1`wombat_tears'
       # extracting the path to the source file and the line number.
-      (file, line_no) = source_line.match(/(.*):(\d+):?.*$/).to_a[1,2] if source_line
       if cookbook_name && recipe_name && source_line
-        "#{cookbook_name}::#{recipe_name} line #{line_no}"
+        "#{cookbook_name}::#{recipe_name} line #{source_line_number}"
       elsif source_line
-        "#{file} line #{line_no}"
+        "#{source_line_file} line #{source_line_number}"
       else
         "dynamically defined"
       end
@@ -1016,8 +1431,8 @@ class Chef
     end
 
     def provider_for_action(action)
-      require 'chef/provider_resolver'
-      provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
+      provider_class = Chef::ProviderResolver.new(node, self, action).resolve
+      provider = provider_class.new(self, run_context)
       provider.action = action
       provider
     end
@@ -1090,30 +1505,44 @@ class Chef
     # === Returns
     # <Chef::Resource>:: returns the proper Chef::Resource class
     def self.resource_for_node(short_name, node)
-      require 'chef/resource_resolver'
-      klass = Chef::ResourceResolver.new(node, short_name).resolve
+      klass = Chef::ResourceResolver.resolve(short_name, node: node)
       raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil?
       klass
     end
 
-    # Returns the class of a Chef::Resource based on the short name
+    #
+    # Returns the class with the given resource_name.
+    #
     # ==== Parameters
     # short_name<Symbol>:: short_name of the resource (ie :directory)
     #
     # === Returns
     # <Chef::Resource>:: returns the proper Chef::Resource class
+    #
     def self.resource_matching_short_name(short_name)
-      begin
-        rname = convert_to_class_name(short_name.to_s)
-        Chef::Resource.const_get(rname)
-      rescue NameError
-        nil
+      Chef::ResourceResolver.resolve(short_name, canonical: true)
+    end
+
+    # @api private
+    def self.register_deprecated_lwrp_class(resource_class, class_name)
+      if Chef::Resource.const_defined?(class_name, false)
+        Chef::Log.warn "#{class_name} already exists!  Deprecation class overwrites #{resource_class}"
+        Chef::Resource.send(:remove_const, class_name)
       end
+
+      if !Chef::Config[:treat_deprecation_warnings_as_errors]
+        Chef::Resource.const_set(class_name, resource_class)
+        deprecated_constants[class_name.to_sym] = resource_class
+      end
+
     end
 
-    private
+    def self.deprecated_constants
+      @deprecated_constants ||= {}
+    end
 
-    def lookup_provider_constant(name)
+    # @api private
+    def lookup_provider_constant(name, action=:nothing)
       begin
         self.class.provider_base.const_get(convert_to_class_name(name.to_s))
       rescue NameError => e
@@ -1124,5 +1553,19 @@ class Chef
         end
       end
     end
+
+    private
+
+    def self.remove_canonical_dsl
+      if @resource_name
+        remaining = Chef.resource_handler_map.delete_canonical(@resource_name, self)
+        if !remaining
+          Chef::DSL::Resources.remove_resource_dsl(@resource_name)
+        end
+      end
+    end
   end
 end
+
+# Requiring things at the bottom breaks cycles
+require 'chef/chef_class'
diff --git a/lib/chef/resource/action_class.rb b/lib/chef/resource/action_class.rb
new file mode 100644
index 0000000..32652dd
--- /dev/null
+++ b/lib/chef/resource/action_class.rb
@@ -0,0 +1,87 @@
+#
+# Author:: John Keiser (<jkeiser at chef.io)
+# Copyright:: Copyright (c) 2015 Opscode, 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/exceptions'
+
+class Chef
+  class Resource
+    module ActionClass
+      def to_s
+        "#{new_resource || "<no resource>"} action #{action ? action.inspect : "<no action>"}"
+      end
+
+      #
+      # If load_current_value! is defined on the resource, use that.
+      #
+      def load_current_resource
+        if new_resource.respond_to?(:load_current_value!)
+          # dup the resource and then reset desired-state properties.
+          current_resource = new_resource.dup
+
+          # We clear desired state in the copy, because it is supposed to be actual state.
+          # We keep identity properties and non-desired-state, which are assumed to be
+          # "control" values like `recurse: true`
+          current_resource.class.properties.each do |name,property|
+            if property.desired_state? && !property.identity? && !property.name_property?
+              property.reset(current_resource)
+            end
+          end
+
+          # Call the actual load_current_value! method. If it raises
+          # CurrentValueDoesNotExist, set current_resource to `nil`.
+          begin
+            # If the user specifies load_current_value do |desired_resource|, we
+            # pass in the desired resource as well as the current one.
+            if current_resource.method(:load_current_value!).arity > 0
+              current_resource.load_current_value!(new_resource)
+            else
+              current_resource.load_current_value!
+            end
+          rescue Chef::Exceptions::CurrentValueDoesNotExist
+            current_resource = nil
+          end
+        end
+
+        @current_resource = current_resource
+      end
+
+      def self.included(other)
+        other.extend(ClassMethods)
+        other.use_inline_resources
+        other.include_resource_dsl true
+      end
+
+      module ClassMethods
+        #
+        # The Chef::Resource class this ActionClass was declared against.
+        #
+        # @return [Class] The Chef::Resource class this ActionClass was declared against.
+        #
+        attr_accessor :resource_class
+
+        def to_s
+          "#{resource_class} action provider"
+        end
+
+        def inspect
+          to_s
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb
index f944825..ca119b5 100644
--- a/lib/chef/resource/apt_package.rb
+++ b/lib/chef/resource/apt_package.rb
@@ -23,12 +23,10 @@ class Chef
   class Resource
     class AptPackage < Chef::Resource::Package
 
-      provides :apt_package
       provides :package, os: "linux", platform_family: [ "debian" ]
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :apt_package
         @default_release = nil
       end
 
diff --git a/lib/chef/resource/bash.rb b/lib/chef/resource/bash.rb
index 0add0ce..025687e 100644
--- a/lib/chef/resource/bash.rb
+++ b/lib/chef/resource/bash.rb
@@ -25,7 +25,6 @@ class Chef
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :bash
         @interpreter = "bash"
       end
 
diff --git a/lib/chef/resource/batch.rb b/lib/chef/resource/batch.rb
index c091ec5..efe3f22 100644
--- a/lib/chef/resource/batch.rb
+++ b/lib/chef/resource/batch.rb
@@ -25,7 +25,7 @@ class Chef
       provides :batch, os: "windows"
 
       def initialize(name, run_context=nil)
-        super(name, run_context, :batch, "cmd.exe")
+        super(name, run_context, nil, "cmd.exe")
       end
 
     end
diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/resource/bff_package.rb
index 917f0d1..7c1496a 100644
--- a/lib/chef/resource/bff_package.rb
+++ b/lib/chef/resource/bff_package.rb
@@ -22,14 +22,6 @@ require 'chef/provider/package/aix'
 class Chef
   class Resource
     class BffPackage < Chef::Resource::Package
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :bff_package
-      end
-
     end
   end
 end
-
-
diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb
index b221026..69dbc48 100644
--- a/lib/chef/resource/breakpoint.rb
+++ b/lib/chef/resource/breakpoint.rb
@@ -22,14 +22,12 @@ require 'chef/resource'
 class Chef
   class Resource
     class Breakpoint < Chef::Resource
+      default_action :break
 
       def initialize(action="break", *args)
-        @name = caller.first
-        super(@name, *args)
-        @action = "break"
-        @allowed_actions << :break
-        @resource_name = :breakpoint
+        super(caller.first, *args)
       end
+
     end
   end
 end
diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb
index 59f575a..7e9d21e 100644
--- a/lib/chef/resource/chef_gem.rb
+++ b/lib/chef/resource/chef_gem.rb
@@ -23,11 +23,8 @@ class Chef
   class Resource
     class ChefGem < Chef::Resource::Package::GemPackage
 
-      provides :chef_gem
-
       def initialize(name, run_context=nil)
         super
-        @resource_name = :chef_gem
         @compile_time = Chef::Config[:chef_gem_compile_time]
         @gem_binary = RbConfig::CONFIG['bindir'] + "/gem"
       end
@@ -53,9 +50,9 @@ class Chef
         # Chef::Resource.run_action: Caveat: this skips Chef::Runner.run_action, where notifications are handled
         # Action could be an array of symbols, but probably won't (think install + enable for a package)
         if compile_time.nil?
-          Chef::Log.deprecation "#{self} chef_gem compile_time installation is deprecated"
-          Chef::Log.deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior."
-          Chef::Log.deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required."
+          Chef.log_deprecation "#{self} chef_gem compile_time installation is deprecated"
+          Chef.log_deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior."
+          Chef.log_deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required."
         end
 
         if compile_time || compile_time.nil?
diff --git a/lib/chef/resource/cookbook_file.rb b/lib/chef/resource/cookbook_file.rb
index 7be353b..42f16e6 100644
--- a/lib/chef/resource/cookbook_file.rb
+++ b/lib/chef/resource/cookbook_file.rb
@@ -27,13 +27,11 @@ class Chef
     class CookbookFile < Chef::Resource::File
       include Chef::Mixin::Securable
 
-      provides :cookbook_file
+      default_action :create
 
       def initialize(name, run_context=nil)
         super
         @provider = Chef::Provider::CookbookFile
-        @resource_name = :cookbook_file
-        @action = "create"
         @source = ::File.basename(name)
         @cookbook = nil
       end
diff --git a/lib/chef/resource/cron.rb b/lib/chef/resource/cron.rb
index cb16506..93cf41b 100644
--- a/lib/chef/resource/cron.rb
+++ b/lib/chef/resource/cron.rb
@@ -27,13 +27,11 @@ class Chef
 
       state_attrs :minute, :hour, :day, :month, :weekday, :user
 
-      provides :cron
+      default_action :create
+      allowed_actions :create, :delete
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :cron
-        @action = :create
-        @allowed_actions.push(:create, :delete)
         @minute = "*"
         @hour = "*"
         @day = "*"
diff --git a/lib/chef/resource/csh.rb b/lib/chef/resource/csh.rb
index 36659c3..d5e9c91 100644
--- a/lib/chef/resource/csh.rb
+++ b/lib/chef/resource/csh.rb
@@ -25,7 +25,6 @@ class Chef
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :csh
         @interpreter = "csh"
       end
 
diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb
index 4252aa2..5df46ff 100644
--- a/lib/chef/resource/deploy.rb
+++ b/lib/chef/resource/deploy.rb
@@ -27,6 +27,7 @@
 #   migration_command "rake db:migrate"
 #   environment "RAILS_ENV" => "production", "OTHER_ENV" => "foo"
 #   shallow_clone true
+#   depth 1
 #   action :deploy # or :rollback
 #   restart_command "touch tmp/restart.txt"
 #   git_ssh_wrapper "wrap-ssh4git.sh"
@@ -51,15 +52,15 @@ class Chef
     #
     class Deploy < Chef::Resource
 
-      provider_base Chef::Provider::Deploy
-
       identity_attr :repository
 
       state_attrs :deploy_to, :revision
 
+      default_action :deploy
+      allowed_actions :force_deploy, :deploy, :rollback
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :deploy
         @deploy_to = name
         @environment = nil
         @repository_cache = 'cached-copy'
@@ -69,15 +70,14 @@ class Chef
         @symlink_before_migrate = {"config/database.yml" => "config/database.yml"}
         @symlinks = {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"}
         @revision = 'HEAD'
-        @action = :deploy
         @migrate = false
         @rollback_on_error = false
         @remote = "origin"
         @enable_submodules = false
         @shallow_clone = false
+        @depth = nil
         @scm_provider = Chef::Provider::Git
         @svn_force_export = false
-        @allowed_actions.push(:force_deploy, :deploy, :rollback)
         @additional_remotes = Hash[]
         @keep_releases = 5
         @enable_checkout = true
@@ -99,8 +99,12 @@ class Chef
         @current_path ||= @deploy_to + "/current"
       end
 
-      def depth
-        @shallow_clone ? "5" : nil
+      def depth(arg=@shallow_clone ? 5 : nil)
+        set_or_return(
+          :depth,
+          arg,
+          :kind_of => [ Integer ]
+        )
       end
 
       # note: deploy_to is your application "meta-root."
@@ -281,6 +285,12 @@ class Chef
         )
       end
 
+      # This is to support "provider :revision" without deprecation warnings.
+      # Do NOT copy this.
+      def self.provider_base
+        Chef::Provider::Deploy
+      end
+
       def svn_force_export(arg=nil)
         set_or_return(
           :svn_force_export,
diff --git a/lib/chef/resource/deploy_revision.rb b/lib/chef/resource/deploy_revision.rb
index e144ce2..1397359 100644
--- a/lib/chef/resource/deploy_revision.rb
+++ b/lib/chef/resource/deploy_revision.rb
@@ -22,23 +22,9 @@ class Chef
     # Convenience class for using the deploy resource with the revision
     # deployment strategy (provider)
     class DeployRevision < Chef::Resource::Deploy
-
-      provides :deploy_revision
-
-      def initialize(*args, &block)
-        super
-        @resource_name = :deploy_revision
-      end
     end
 
     class DeployBranch < Chef::Resource::DeployRevision
-
-      provides :deploy_branch
-
-      def initialize(*args, &block)
-        super
-        @resource_name = :deploy_branch
-      end
     end
 
   end
diff --git a/lib/chef/resource/directory.rb b/lib/chef/resource/directory.rb
index 1ab7f0d..9cac2ce 100644
--- a/lib/chef/resource/directory.rb
+++ b/lib/chef/resource/directory.rb
@@ -32,15 +32,13 @@ class Chef
 
       include Chef::Mixin::Securable
 
-      provides :directory
+      default_action :create
+      allowed_actions :create, :delete
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :directory
         @path = name
-        @action = :create
         @recursive = false
-        @allowed_actions.push(:create, :delete)
       end
 
       def recursive(arg=nil)
diff --git a/lib/chef/resource/dpkg_package.rb b/lib/chef/resource/dpkg_package.rb
index 35a47e8..9288c18 100644
--- a/lib/chef/resource/dpkg_package.rb
+++ b/lib/chef/resource/dpkg_package.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,19 +17,15 @@
 #
 
 require 'chef/resource/package'
-require 'chef/provider/package/dpkg'
 
 class Chef
   class Resource
     class DpkgPackage < Chef::Resource::Package
-
       provides :dpkg_package, os: "linux"
 
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :dpkg_package
-      end
+      resource_name :dpkg_package
 
+      property :source, [String, Array, nil]
     end
   end
 end
diff --git a/lib/chef/resource/dsc_resource.rb b/lib/chef/resource/dsc_resource.rb
index 912b683..0664dc7 100644
--- a/lib/chef/resource/dsc_resource.rb
+++ b/lib/chef/resource/dsc_resource.rb
@@ -20,18 +20,36 @@ require 'chef/dsl/powershell'
 class Chef
   class Resource
     class DscResource < Chef::Resource
-
       provides :dsc_resource, os: "windows"
 
+      # This class will check if the object responds to
+      # to_text. If it does, it will call that as opposed
+      # to inspect. This is useful for properties that hold
+      # objects such as PsCredential, where we do not want
+      # to dump the actual ivars
+      class ToTextHash < Hash
+        def to_text
+          descriptions = self.map do |(property, obj)|
+            obj_text = if obj.respond_to?(:to_text)
+                         obj.to_text
+                       else
+                         obj.inspect
+                       end
+            "#{property}=>#{obj_text}"
+          end
+          "{#{descriptions.join(', ')}}"
+        end
+      end
+
       include Chef::DSL::Powershell
 
+      default_action :run
+
       def initialize(name, run_context)
         super
-        @properties = {}
-        @resource_name = :dsc_resource
+        @properties = ToTextHash.new
         @resource = nil
-        @allowed_actions.push(:run)
-        @action = :run
+        @reboot_action = :nothing
       end
 
       def resource(value=nil)
@@ -69,6 +87,25 @@ class Chef
         end
       end
 
+      # This property takes the action message for the reboot resource
+      # If the set method of the DSC resource indicate that a reboot
+      # is necessary, reboot_action provides the mechanism for a reboot to
+      # be requested.
+      def reboot_action(value=nil)
+        if value
+          @reboot_action = value
+        else
+          @reboot_action
+        end
+      end
+
+      def timeout(arg=nil)
+        set_or_return(
+          :timeout,
+          arg,
+          :kind_of => [ Integer ]
+        )
+      end
       private
 
       def value_of(value)
diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb
index cf96ef6..c3602fa 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -17,18 +17,19 @@
 #
 
 require 'chef/exceptions'
+require 'chef/dsl/powershell'
 
 class Chef
   class Resource
     class DscScript < Chef::Resource
+      include Chef::DSL::Powershell
 
-      provides :dsc_script, platform: "windows"
+      provides :dsc_script, os: "windows"
+
+      default_action :run
 
       def initialize(name, run_context=nil)
         super
-        @allowed_actions.push(:run)
-        @action = :run
-        @resource_name = :dsc_script
         @imports = {}
       end
 
diff --git a/lib/chef/resource/easy_install_package.rb b/lib/chef/resource/easy_install_package.rb
index 5286e9a..df4cee1 100644
--- a/lib/chef/resource/easy_install_package.rb
+++ b/lib/chef/resource/easy_install_package.rb
@@ -22,13 +22,6 @@ class Chef
   class Resource
     class EasyInstallPackage < Chef::Resource::Package
 
-      provides :easy_install_package
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :easy_install_package
-      end
-
       def easy_install_binary(arg=nil)
         set_or_return(
           :easy_install_binary,
diff --git a/lib/chef/resource/env.rb b/lib/chef/resource/env.rb
index 2072ae5..025bfc7 100644
--- a/lib/chef/resource/env.rb
+++ b/lib/chef/resource/env.rb
@@ -27,14 +27,14 @@ class Chef
 
       provides :env, os: "windows"
 
+      default_action :create
+      allowed_actions :create, :delete, :modify
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :env
         @key_name = name
         @value = nil
-        @action = :create
         @delim = nil
-        @allowed_actions.push(:create, :delete, :modify)
       end
 
       def key_name(arg=nil)
diff --git a/lib/chef/resource/erl_call.rb b/lib/chef/resource/erl_call.rb
index 24009d5..1976c54 100644
--- a/lib/chef/resource/erl_call.rb
+++ b/lib/chef/resource/erl_call.rb
@@ -28,18 +28,16 @@ class Chef
 
       identity_attr :code
 
+      default_action :run
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :erl_call
 
         @code = "q()." # your erlang code goes here
         @cookie = nil # cookie of the erlang node
         @distributed = false # if you want to have a distributed erlang node
         @name_type = "sname" # type of erlang hostname name or sname
         @node_name = "chef at localhost" # the erlang node hostname
-
-        @action = "run"
-        @allowed_actions.push(:run)
       end
 
       def code(arg=nil)
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 9f8b629..4f92a7a 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -32,12 +32,12 @@ class Chef
       # Only execute resources (and subclasses) can be guard interpreters.
       attr_accessor :is_guard_interpreter
 
+      default_action :run
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :execute
         @command = name
         @backup = 5
-        @action = "run"
         @creates = nil
         @cwd = nil
         @environment = nil
@@ -46,10 +46,10 @@ class Chef
         @returns = 0
         @timeout = nil
         @user = nil
-        @allowed_actions.push(:run)
         @umask = nil
         @default_guard_interpreter = :execute
         @is_guard_interpreter = false
+        @live_stream = false
       end
 
       def umask(arg=nil)
@@ -102,8 +102,15 @@ class Chef
         )
       end
 
+      def live_stream(arg=nil)
+        set_or_return(
+          :live_stream,
+          arg,
+          :kind_of => [ TrueClass, FalseClass ])
+      end
+
       def path(arg=nil)
-        Chef::Log.warn "'path' attribute of 'execute' is not used by any provider in Chef 11 and Chef 12. Use 'environment' attribute to configure 'PATH'. This attribute will be removed in Chef 13."
+        Chef::Log.warn "The 'path' attribute of 'execute' is not used by any provider in Chef 11 or Chef 12. Use 'environment' attribute to configure 'PATH'. This attribute will be removed in Chef 13."
 
         set_or_return(
           :path,
diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb
index 53a6a16..d278652 100644
--- a/lib/chef/resource/file.rb
+++ b/lib/chef/resource/file.rb
@@ -38,15 +38,22 @@ class Chef
 
       attr_writer :checksum
 
-      provides :file
+      #
+      # The checksum of the rendered file.  This has to be saved on the
+      # new_resource for the 'after' state for reporting but we cannot
+      # mutate the new_resource.checksum which would change the
+      # user intent in the new_resource if the resource is reused.
+      #
+      # @returns [String] Checksum of the file we actually rendered
+      attr_accessor :final_checksum
+
+      default_action :create
+      allowed_actions :create, :delete, :touch, :create_if_missing
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :file
         @path = name
         @backup = 5
-        @action = "create"
-        @allowed_actions.push(:create, :delete, :touch, :create_if_missing)
         @atomic_update = Chef::Config[:file_atomic_update]
         @force_unlink = false
         @manage_symlink_source = nil
@@ -129,6 +136,15 @@ class Chef
           @verifications
         end
       end
+
+      def state_for_resource_reporter
+        state_attrs = super()
+        # fix up checksum state with final_checksum saved by the provider
+        if checksum.nil? && final_checksum
+          state_attrs[:checksum] = final_checksum
+        end
+        state_attrs
+      end
     end
   end
 end
diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb
index f1ca0f1..ba0bb08 100644
--- a/lib/chef/resource/file/verification.rb
+++ b/lib/chef/resource/file/verification.rb
@@ -106,7 +106,13 @@ class Chef
         # We reuse Chef::GuardInterpreter in order to support
         # the same set of options that the not_if/only_if blocks do
         def verify_command(path, opts)
-          command = @command % {:file => path}
+          # First implementation interpolated `file`; docs & RFC claim `path`
+          # is interpolated. Until `file` can be deprecated, interpolate both.
+          Chef.log_deprecation(
+            '%{file} is deprecated in verify command and will not be '\
+            'supported in Chef 13. Please use %{path} instead.'
+          ) if @command.include?('%{file}')
+          command = @command % {:file => path, :path => path}
           interpreter = Chef::GuardInterpreter.for_resource(@parent_resource, command, @command_opts)
           interpreter.evaluate
         end
diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb
index 9c8db50..c7c4345 100644
--- a/lib/chef/resource/freebsd_package.rb
+++ b/lib/chef/resource/freebsd_package.rb
@@ -31,11 +31,6 @@ class Chef
 
       provides :package, platform: "freebsd"
 
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :freebsd_package
-      end
-
       def after_created
         assign_provider
       end
diff --git a/lib/chef/resource/gem_package.rb b/lib/chef/resource/gem_package.rb
index 0e838ca..b981797 100644
--- a/lib/chef/resource/gem_package.rb
+++ b/lib/chef/resource/gem_package.rb
@@ -22,11 +22,8 @@ class Chef
   class Resource
     class GemPackage < Chef::Resource::Package
 
-      provides :gem_package
-
       def initialize(name, run_context=nil)
         super
-        @resource_name = :gem_package
         @clear_sources = false
       end
 
diff --git a/lib/chef/resource/git.rb b/lib/chef/resource/git.rb
index 7156873..393a068 100644
--- a/lib/chef/resource/git.rb
+++ b/lib/chef/resource/git.rb
@@ -22,11 +22,8 @@ class Chef
   class Resource
     class Git < Chef::Resource::Scm
 
-      provides :git
-
       def initialize(name, run_context=nil)
         super
-        @resource_name = :git
         @additional_remotes = Hash[]
       end
 
diff --git a/lib/chef/resource/group.rb b/lib/chef/resource/group.rb
index 9e8f130..2e80f32 100644
--- a/lib/chef/resource/group.rb
+++ b/lib/chef/resource/group.rb
@@ -25,19 +25,17 @@ class Chef
 
       state_attrs :members
 
-      provides :group
+      allowed_actions :create, :remove, :modify, :manage
+      default_action :create
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :group
         @group_name = name
         @gid = nil
         @members = []
         @excluded_members = []
-        @action = :create
         @append = false
         @non_unique = false
-        @allowed_actions.push(:create, :remove, :modify, :manage)
       end
 
       def group_name(arg=nil)
diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb
index 73409b1..048ba6b 100644
--- a/lib/chef/resource/homebrew_package.rb
+++ b/lib/chef/resource/homebrew_package.rb
@@ -25,12 +25,10 @@ class Chef
   class Resource
     class HomebrewPackage < Chef::Resource::Package
 
-      provides :homebrew_package
       provides :package, os: "darwin"
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :homebrew_package
         @homebrew_user = nil
       end
 
diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb
index ccb0a26..f9f0563 100644
--- a/lib/chef/resource/http_request.rb
+++ b/lib/chef/resource/http_request.rb
@@ -26,14 +26,14 @@ class Chef
 
       identity_attr :url
 
+      default_action :get
+      allowed_actions :get, :put, :post, :delete, :head, :options
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :http_request
         @message = name
         @url = nil
-        @action = :get
         @headers = {}
-        @allowed_actions.push(:get, :put, :post, :delete, :head, :options)
       end
 
       def url(args=nil)
diff --git a/lib/chef/resource/ifconfig.rb b/lib/chef/resource/ifconfig.rb
index c289dda..527eb0e 100644
--- a/lib/chef/resource/ifconfig.rb
+++ b/lib/chef/resource/ifconfig.rb
@@ -27,12 +27,12 @@ class Chef
 
       state_attrs :inet_addr, :mask
 
+      default_action :add
+      allowed_actions :add, :delete, :enable, :disable
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :ifconfig
         @target = name
-        @action = :add
-        @allowed_actions.push(:add, :delete, :enable, :disable)
         @hwaddr = nil
         @mask = nil
         @inet_addr = nil
@@ -145,5 +145,3 @@ class Chef
 
   end
 end
-
-
diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb
index c0e699e..2bf8e1d 100644
--- a/lib/chef/resource/ips_package.rb
+++ b/lib/chef/resource/ips_package.rb
@@ -23,12 +23,13 @@ class Chef
   class Resource
     class IpsPackage < ::Chef::Resource::Package
 
+      provides :package, os: "solaris2"
       provides :ips_package, os: "solaris2"
 
+      allowed_actions :install, :remove, :upgrade
+
       def initialize(name, run_context = nil)
         super(name, run_context)
-        @resource_name = :ips_package
-        @allowed_actions.push(:install, :remove, :upgrade)
         @accept_license = false
       end
 
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/resource/ksh.rb
similarity index 77%
copy from lib/chef/resource/whyrun_safe_ruby_block.rb
copy to lib/chef/resource/ksh.rb
index 6fa5383..b41b717 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/resource/ksh.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Phil Dibowitz (<phild at fb.com>)
-# Copyright:: Copyright (c) 2013 Facebook
+# Author:: Nolan Davidson (<nolan.davidson at gmail.com>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,15 @@
 # limitations under the License.
 #
 
+require 'chef/resource/script'
+
 class Chef
   class Resource
-    class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
+    class Ksh < Chef::Resource::Script
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :whyrun_safe_ruby_block
+        @interpreter = "ksh"
       end
 
     end
diff --git a/lib/chef/resource/link.rb b/lib/chef/resource/link.rb
index 30f8ec8..f932383 100644
--- a/lib/chef/resource/link.rb
+++ b/lib/chef/resource/link.rb
@@ -25,21 +25,19 @@ class Chef
     class Link < Chef::Resource
       include Chef::Mixin::Securable
 
-      provides :link
-
       identity_attr :target_file
 
       state_attrs :to, :owner, :group
 
+      default_action :create
+      allowed_actions :create, :delete
+
       def initialize(name, run_context=nil)
         verify_links_supported!
         super
-        @resource_name = :link
         @to = nil
-        @action = :create
         @link_type = :symbolic
         @target_file = name
-        @allowed_actions.push(:create, :delete)
       end
 
       def to(arg=nil)
diff --git a/lib/chef/resource/log.rb b/lib/chef/resource/log.rb
index 7f970a8..9adffb2 100644
--- a/lib/chef/resource/log.rb
+++ b/lib/chef/resource/log.rb
@@ -26,6 +26,8 @@ class Chef
 
       identity_attr :message
 
+      default_action :write
+
       # Sends a string from a recipe to a log provider
       #
       # log "some string to log" do
@@ -48,10 +50,7 @@ class Chef
       # node<Chef::Node>:: Node where resource will be used
       def initialize(name, run_context=nil)
         super
-        @resource_name = :log
         @level = :info
-        @action = :write
-        @allowed_actions.push(:write)
         @message = name
       end
 
@@ -75,5 +74,3 @@ class Chef
     end
   end
 end
-
-
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index ce72e98..c743f12 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -1,8 +1,8 @@
 #
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Christopher Walters (<cw at opscode.com>)
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2008-2012 Opscode, Inc.
+# Author:: Adam Jacob (<adam at chef.io>)
+# Author:: Christopher Walters (<cw at chef.io>)
+# Author:: Daniel DeLeo (<dan at chef.io>)
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,6 +19,13 @@
 #
 
 require 'chef/resource'
+require 'chef/resource_resolver'
+require 'chef/node'
+require 'chef/log'
+require 'chef/exceptions'
+require 'chef/mixin/convert_to_class_name'
+require 'chef/mixin/from_file'
+require 'chef/mixin/params_validate' # for DelayedEvaluator
 
 class Chef
   class Resource
@@ -28,138 +35,90 @@ class Chef
     # so attributes, default action, etc. can be defined with pleasing syntax.
     class LWRPBase < Resource
 
-      NULL_ARG = Object.new
+      # Class methods
+      class <<self
 
-      extend Chef::Mixin::ConvertToClassName
-      extend Chef::Mixin::FromFile
+        include Chef::Mixin::ConvertToClassName
+        include Chef::Mixin::FromFile
 
-      # Evaluates the LWRP resource file and instantiates a new Resource class.
-      def self.build_from_file(cookbook_name, filename, run_context)
-        resource_class = nil
-        rname = filename_to_qualified_string(cookbook_name, filename)
+        attr_accessor :loaded_lwrps
 
-        class_name = convert_to_class_name(rname)
-        if Resource.const_defined?(class_name, false)
-          Chef::Log.info("#{class_name} light-weight resource is already initialized -- Skipping loading #{filename}!")
-          Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.")
-          resource_class = Resource.const_get(class_name)
-        else
-          resource_class = Class.new(self)
+        def build_from_file(cookbook_name, filename, run_context)
+          if LWRPBase.loaded_lwrps[filename]
+            Chef::Log.info("Custom resource #{filename} from cookbook #{cookbook_name} has already been loaded!  Skipping the reload.")
+            return loaded_lwrps[filename]
+          end
 
-          Chef::Resource.const_set(class_name, resource_class)
-          resource_class.resource_name = rname
+          resource_name = filename_to_qualified_string(cookbook_name, filename)
+
+          # We load the class first to give it a chance to set its own name
+          resource_class = Class.new(self)
+          resource_class.resource_name resource_name.to_sym
           resource_class.run_context = run_context
           resource_class.class_from_file(filename)
 
-          Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}")
-        end
-
-        resource_class
-      end
+          # Make a useful string for the class (rather than <Class:312894723894>)
+          resource_class.instance_eval do
+            define_singleton_method(:to_s) do
+              "Custom resource #{resource_name} from cookbook #{cookbook_name}"
+            end
+            define_singleton_method(:inspect) { to_s }
+          end
 
-      # Set the resource name for this LWRP
-      def self.resource_name(arg = NULL_ARG)
-        if arg.equal?(NULL_ARG)
-          @resource_name
-        else
-          @resource_name = arg
-        end
-      end
+          Chef::Log.debug("Loaded contents of #{filename} into resource #{resource_name} (#{resource_class})")
 
-      class << self
-        alias_method :resource_name=, :resource_name
-      end
+          LWRPBase.loaded_lwrps[filename] = true
 
-      # Define an attribute on this resource, including optional validation
-      # parameters.
-      def self.attribute(attr_name, validation_opts={})
-        define_method(attr_name) do |arg=nil|
-          set_or_return(attr_name.to_sym, arg, validation_opts)
+          # Create the deprecated Chef::Resource::LwrpFoo class
+          Chef::Resource.register_deprecated_lwrp_class(resource_class, convert_to_class_name(resource_name))
+          resource_class
         end
-      end
 
-      # Sets the default action
-      def self.default_action(action_name=NULL_ARG)
-        unless action_name.equal?(NULL_ARG)
-          @actions ||= []
-          if action_name.is_a?(Array)
-            action = action_name.map { |arg| arg.to_sym }
-            @actions = actions | action
-            @default_action = action
-          else
-            action = action_name.to_sym
-            @actions.push(action) unless @actions.include?(action)
-            @default_action = action
-          end
-        end
+        alias :attribute :property
 
-        @default_action ||= from_superclass(:default_action)
-      end
-
-      # Adds +action_names+ to the list of valid actions for this resource.
-      def self.actions(*action_names)
-        if action_names.empty?
-          defined?(@actions) ? @actions : from_superclass(:actions, []).dup
-        else
-          # BC-compat way for checking if actions have already been defined
-          if defined?(@actions)
-            @actions.push(*action_names)
+        # Adds +action_names+ to the list of valid actions for this resource.
+        # Does not include superclass's action list when appending.
+        def actions(*action_names)
+          action_names = action_names.flatten
+          if !action_names.empty? && !@allowed_actions
+            self.allowed_actions = ([ :nothing ] + action_names).uniq
           else
-            @actions = action_names
+            allowed_actions(*action_names)
           end
         end
-      end
-
-      # @deprecated
-      def self.valid_actions(*args)
-        Chef::Log.warn("`valid_actions' is deprecated, please use actions `instead'!")
-        actions(*args)
-      end
+        alias :actions= :allowed_actions=
 
-      # Set the run context on the class. Used to provide access to the node
-      # during class definition.
-      def self.run_context=(run_context)
-        @run_context = run_context
-      end
+        # @deprecated
+        def valid_actions(*args)
+          Chef::Log.warn("`valid_actions' is deprecated, please use allowed_actions `instead'!")
+          allowed_actions(*args)
+        end
 
-      def self.run_context
-        @run_context
-      end
+        # Set the run context on the class. Used to provide access to the node
+        # during class definition.
+        attr_accessor :run_context
 
-      def self.node
-        run_context.node
-      end
-
-      def self.lazy(&block)
-        DelayedEvaluator.new(&block)
-      end
+        def node
+          run_context ? run_context.node : nil
+        end
 
-      private
+        protected
 
-      # Get the value from the superclass, if it responds, otherwise return
-      # +nil+. Since class instance variables are **not** inherited upon
-      # subclassing, this is a required check to ensure Chef pulls the
-      # +default_action+ and other DSL-y methods when extending LWRP::Base.
-      def self.from_superclass(m, default = nil)
-        return default if superclass == Chef::Resource::LWRPBase
-        superclass.respond_to?(m) ? superclass.send(m) : default
-      end
+        def loaded_lwrps
+          @loaded_lwrps ||= {}
+        end
 
-      # Default initializer. Sets the default action and allowed actions.
-      def initialize(name, run_context=nil)
-        super(name, run_context)
+        private
 
-        # Raise an exception if the resource_name was not defined
-        if self.class.resource_name.nil?
-          raise Chef::Exceptions::InvalidResourceSpecification,
-            "You must specify `resource_name'!"
+        # Get the value from the superclass, if it responds, otherwise return
+        # +nil+. Since class instance variables are **not** inherited upon
+        # subclassing, this is a required check to ensure Chef pulls the
+        # +default_action+ and other DSL-y methods when extending LWRP::Base.
+        def from_superclass(m, default = nil)
+          return default if superclass == Chef::Resource::LWRPBase
+          superclass.respond_to?(m) ? superclass.send(m) : default
         end
-
-        @resource_name = self.class.resource_name.to_sym
-        @action = self.class.default_action
-        allowed_actions.push(self.class.actions).flatten!
       end
-
     end
   end
 end
diff --git a/lib/chef/resource/macosx_service.rb b/lib/chef/resource/macosx_service.rb
index 879ea99..f1ed405 100644
--- a/lib/chef/resource/macosx_service.rb
+++ b/lib/chef/resource/macosx_service.rb
@@ -22,8 +22,8 @@ class Chef
   class Resource
     class MacosxService < Chef::Resource::Service
 
-      provides :service, os: "darwin"
       provides :macosx_service, os: "darwin"
+      provides :service, os: "darwin"
 
       identity_attr :service_name
 
@@ -31,7 +31,6 @@ class Chef
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :macosx_service
         @plist = nil
         @session_type = nil
       end
diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb
index 0d4e5de..5843016 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -16,17 +16,11 @@
 # limitations under the License.
 #
 
+require 'chef/resource/package'
+
 class Chef
   class Resource
     class MacportsPackage < Chef::Resource::Package
-
-      provides :macports_package
-      provides :package, os: "darwin"
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :macports_package
-      end
     end
   end
 end
diff --git a/lib/chef/resource/mdadm.rb b/lib/chef/resource/mdadm.rb
index 971b6c5..b789fab 100644
--- a/lib/chef/resource/mdadm.rb
+++ b/lib/chef/resource/mdadm.rb
@@ -27,11 +27,11 @@ class Chef
 
       state_attrs :devices, :level, :chunk
 
-      provides :mdadm
+      default_action :create
+      allowed_actions :create, :assemble, :stop
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :mdadm
 
         @chunk = 16
         @devices = []
@@ -40,9 +40,6 @@ class Chef
         @metadata = "0.90"
         @bitmap = nil
         @raid_device = name
-
-        @action = :create
-        @allowed_actions.push(:create, :assemble, :stop)
       end
 
       def chunk(arg=nil)
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index 142dec8..a5da0ba 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -27,11 +27,11 @@ class Chef
 
       state_attrs :mount_point, :device_type, :fstype, :username, :password, :domain
 
-      provides :mount
+      default_action :mount
+      allowed_actions :mount, :umount, :remount, :enable, :disable
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :mount
         @mount_point = name
         @device = nil
         @device_type = :device
@@ -42,9 +42,7 @@ class Chef
         @pass = 2
         @mounted = false
         @enabled = false
-        @action = :mount
         @supports = { :remount => false }
-        @allowed_actions.push(:mount, :umount, :remount, :enable, :disable)
         @username = nil
         @password = nil
         @domain = nil
@@ -176,6 +174,14 @@ class Chef
         )
       end
 
+      private
+
+      # Used by the AIX provider to set fstype to nil.
+      # TODO use property to make nil a valid value for fstype
+      def clear_fstype
+        @fstype = nil
+      end
+
     end
   end
 end
diff --git a/lib/chef/resource/ohai.rb b/lib/chef/resource/ohai.rb
index b567db4..9425e55 100644
--- a/lib/chef/resource/ohai.rb
+++ b/lib/chef/resource/ohai.rb
@@ -25,12 +25,11 @@ class Chef
 
       state_attrs :plugin
 
+      default_action :reload
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :ohai
         @name = name
-        @allowed_actions.push(:reload)
-        @action = :reload
         @plugin = nil
       end
 
diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb
index 20a2523..9ae8813 100644
--- a/lib/chef/resource/openbsd_package.rb
+++ b/lib/chef/resource/openbsd_package.rb
@@ -29,23 +29,6 @@ class Chef
       include Chef::Mixin::ShellOut
 
       provides :package, os: "openbsd"
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :openbsd_package
-      end
-
-      def after_created
-        assign_provider
-      end
-
-      private
-
-      def assign_provider
-        @provider = Chef::Provider::Package::Openbsd
-      end
-
     end
   end
 end
-
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index f4f49b5..c73810a 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -1,7 +1,7 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Tyler Cloke (<tyler at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,24 +22,22 @@ require 'chef/resource'
 class Chef
   class Resource
     class Package < Chef::Resource
-
       identity_attr :package_name
 
       state_attrs :version, :options
 
+      default_action :install
+      allowed_actions :install, :upgrade, :remove, :purge, :reconfig
+
       def initialize(name, run_context=nil)
         super
-        @action = :install
-        @allowed_actions.push(:install, :upgrade, :remove, :purge, :reconfig)
-        @candidate_version = nil
-        @options = nil
-        @package_name = name
-        @resource_name = :package
-        @response_file = nil
-        @response_file_variables = Hash.new
-        @source = nil
-        @version = nil
-        @timeout = 900
+        options                 nil
+        package_name            name
+        response_file           nil
+        response_file_variables Hash.new
+        source                  nil
+        version                 nil
+        timeout                 nil
       end
 
       def package_name(arg=nil)
diff --git a/lib/chef/resource/pacman_package.rb b/lib/chef/resource/pacman_package.rb
index 4c45dd0..54b8efc 100644
--- a/lib/chef/resource/pacman_package.rb
+++ b/lib/chef/resource/pacman_package.rb
@@ -21,14 +21,7 @@ require 'chef/resource/package'
 class Chef
   class Resource
     class PacmanPackage < Chef::Resource::Package
-
       provides :pacman_package, os: "linux"
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :pacman_package
-      end
-
     end
   end
 end
diff --git a/lib/chef/resource/paludis_package.rb b/lib/chef/resource/paludis_package.rb
index 552c968..56c47bc 100644
--- a/lib/chef/resource/paludis_package.rb
+++ b/lib/chef/resource/paludis_package.rb
@@ -22,13 +22,12 @@ require 'chef/provider/package/paludis'
 class Chef
   class Resource
     class PaludisPackage < Chef::Resource::Package
-
       provides :paludis_package, os: "linux"
 
+      allowed_actions :install, :remove, :upgrade
+
       def initialize(name, run_context=nil)
         super(name, run_context)
-        @resource_name = :paludis_package
-        @allowed_actions.push(:install, :remove, :upgrade)
         @timeout = 3600
       end
     end
diff --git a/lib/chef/resource/perl.rb b/lib/chef/resource/perl.rb
index c4bdb6e..773eba6 100644
--- a/lib/chef/resource/perl.rb
+++ b/lib/chef/resource/perl.rb
@@ -22,10 +22,8 @@ require 'chef/provider/script'
 class Chef
   class Resource
     class Perl < Chef::Resource::Script
-
       def initialize(name, run_context=nil)
         super
-        @resource_name = :perl
         @interpreter = "perl"
       end
 
diff --git a/lib/chef/resource/portage_package.rb b/lib/chef/resource/portage_package.rb
index 42c0356..1af4870 100644
--- a/lib/chef/resource/portage_package.rb
+++ b/lib/chef/resource/portage_package.rb
@@ -21,10 +21,8 @@ require 'chef/resource/package'
 class Chef
   class Resource
     class PortagePackage < Chef::Resource::Package
-
       def initialize(name, run_context=nil)
         super
-        @resource_name = :portage_package
         @provider = Chef::Provider::Package::Portage
       end
 
diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb
index 43aafe4..7d43288 100644
--- a/lib/chef/resource/powershell_script.rb
+++ b/lib/chef/resource/powershell_script.rb
@@ -20,11 +20,10 @@ require 'chef/resource/windows_script'
 class Chef
   class Resource
     class PowershellScript < Chef::Resource::WindowsScript
-
       provides :powershell_script, os: "windows"
 
       def initialize(name, run_context=nil)
-        super(name, run_context, :powershell_script, "powershell.exe")
+        super(name, run_context, nil, "powershell.exe")
         @convert_boolean_return = false
       end
 
diff --git a/lib/chef/resource/python.rb b/lib/chef/resource/python.rb
index b1f23d1..432ee46 100644
--- a/lib/chef/resource/python.rb
+++ b/lib/chef/resource/python.rb
@@ -21,10 +21,8 @@ require 'chef/provider/script'
 class Chef
   class Resource
     class Python < Chef::Resource::Script
-
       def initialize(name, run_context=nil)
         super
-        @resource_name = :python
         @interpreter = "python"
       end
 
diff --git a/lib/chef/resource/reboot.rb b/lib/chef/resource/reboot.rb
index c111b23..401f2f3 100644
--- a/lib/chef/resource/reboot.rb
+++ b/lib/chef/resource/reboot.rb
@@ -24,11 +24,11 @@ require 'chef/resource'
 class Chef
   class Resource
     class Reboot < Chef::Resource
+      allowed_actions :request_reboot, :reboot_now, :cancel
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :reboot
         @provider = Chef::Provider::Reboot
-        @allowed_actions.push(:request_reboot, :reboot_now, :cancel)
 
         @reason = "Reboot by Chef"
         @delay_mins = 0
diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb
index 8126ccf..f1bf795 100644
--- a/lib/chef/resource/registry_key.rb
+++ b/lib/chef/resource/registry_key.rb
@@ -22,10 +22,12 @@ require 'chef/digester'
 class Chef
   class Resource
     class RegistryKey < Chef::Resource
-
       identity_attr :key
       state_attrs :values
 
+      default_action :create
+      allowed_actions :create, :create_if_missing, :delete, :delete_key
+
       # Some registry key data types may not be safely reported as json.
       # Example (CHEF-5323):
       #
@@ -59,13 +61,10 @@ class Chef
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :registry_key
-        @action = :create
         @architecture = :machine
         @recursive = false
         @key = name
         @values, @unscrubbed_values = [], []
-        @allowed_actions.push(:create, :create_if_missing, :delete, :delete_key)
       end
 
       def key(arg=nil)
@@ -94,7 +93,7 @@ class Chef
               raise ArgumentError, "Bad key #{key} in RegistryKey values hash" unless [:name,:type,:data].include?(key)
             end
             raise ArgumentError, "Type of name => #{v[:name]} should be string" unless v[:name].is_a?(String)
-            raise Argument Error "Type of type => #{v[:name]} should be symbol" unless v[:type].is_a?(Symbol)
+            raise ArgumentError, "Type of type => #{v[:type]} should be symbol" unless v[:type].is_a?(Symbol)
           end
           @unscrubbed_values = @values
         elsif self.instance_variable_defined?(:@values)
@@ -126,7 +125,7 @@ class Chef
           scrubbed_value = value.dup
           if needs_checksum?(scrubbed_value)
             data_io = StringIO.new(scrubbed_value[:data].to_s)
-            scrubbed_value[:data] = Chef::Digester.instance.generate_md5_checksum(data_io)
+            scrubbed_value[:data] = Chef::Digester.instance.generate_checksum(data_io)
           end
           scrubbed << scrubbed_value
         end
diff --git a/lib/chef/resource/remote_directory.rb b/lib/chef/resource/remote_directory.rb
index d4108da..b731f7b 100644
--- a/lib/chef/resource/remote_directory.rb
+++ b/lib/chef/resource/remote_directory.rb
@@ -26,19 +26,18 @@ class Chef
     class RemoteDirectory < Chef::Resource::Directory
       include Chef::Mixin::Securable
 
-      provides :remote_directory
-
       identity_attr :path
 
       state_attrs :files_owner, :files_group, :files_mode
 
+      default_action :create
+      allowed_actions :create, :create_if_missing, :delete
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :remote_directory
         @path = name
         @source = ::File.basename(name)
         @delete = false
-        @action = :create
         @recursive = true
         @purge = false
         @files_backup = 5
@@ -46,7 +45,6 @@ class Chef
         @files_group = nil
         @files_mode = 0644 unless Chef::Platform.windows?
         @overwrite = true
-        @allowed_actions.push(:create, :create_if_missing, :delete)
         @cookbook = nil
       end
 
diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb
index e56f699..b7a553c 100644
--- a/lib/chef/resource/remote_file.rb
+++ b/lib/chef/resource/remote_file.rb
@@ -21,18 +21,15 @@ require 'uri'
 require 'chef/resource/file'
 require 'chef/provider/remote_file'
 require 'chef/mixin/securable'
+require 'chef/mixin/uris'
 
 class Chef
   class Resource
     class RemoteFile < Chef::Resource::File
       include Chef::Mixin::Securable
 
-      provides :remote_file
-
       def initialize(name, run_context=nil)
         super
-        @resource_name = :remote_file
-        @action = "create"
         @source = []
         @use_etag = true
         @use_last_modified = true
@@ -127,6 +124,8 @@ class Chef
 
       private
 
+      include Chef::Mixin::Uris
+
       def validate_source(source)
         source = Array(source).flatten
         raise ArgumentError, "#{resource_name} has an empty source" if source.empty?
@@ -140,7 +139,7 @@ class Chef
       end
 
       def absolute_uri?(source)
-        source.kind_of?(String) and URI.parse(source).absolute?
+        Chef::Provider::RemoteFile::Fetcher.network_share?(source) or (source.kind_of?(String) and as_uri(source).absolute?)
       rescue URI::InvalidURIError
         false
       end
diff --git a/lib/chef/resource/resource_notification.rb b/lib/chef/resource/resource_notification.rb
index a27ed96..4fd61ad 100644
--- a/lib/chef/resource/resource_notification.rb
+++ b/lib/chef/resource/resource_notification.rb
@@ -20,7 +20,15 @@ require 'chef/resource'
 
 class Chef
   class Resource
-    class Notification < Struct.new(:resource, :action, :notifying_resource)
+    class Notification
+
+      attr_accessor :resource, :action, :notifying_resource
+
+      def initialize(resource, action, notifying_resource)
+        @resource = resource
+        @action = action
+        @notifying_resource = notifying_resource
+      end
 
       def duplicates?(other_notification)
         unless other_notification.respond_to?(:resource) && other_notification.respond_to?(:action)
@@ -104,6 +112,11 @@ is defined near #{resource.source_line}
         raise err
       end
 
+      def ==(other)
+        return false unless other.is_a?(self.class)
+        other.resource == resource && other.action == action && other.notifying_resource == notifying_resource
+      end
+
     end
   end
 end
diff --git a/lib/chef/resource/route.rb b/lib/chef/resource/route.rb
index 942905d..3ba8f62 100644
--- a/lib/chef/resource/route.rb
+++ b/lib/chef/resource/route.rb
@@ -22,17 +22,16 @@ require 'chef/resource'
 class Chef
   class Resource
     class Route < Chef::Resource
-
       identity_attr :target
 
       state_attrs :netmask, :gateway
 
+      default_action :add
+      allowed_actions :add, :delete
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :route
         @target = name
-        @action = [:add]
-        @allowed_actions.push(:add, :delete)
         @netmask = nil
         @gateway = nil
         @metric = nil
@@ -136,5 +135,3 @@ class Chef
     end
   end
 end
-
-
diff --git a/lib/chef/resource/rpm_package.rb b/lib/chef/resource/rpm_package.rb
index f00121d..b8b5144 100644
--- a/lib/chef/resource/rpm_package.rb
+++ b/lib/chef/resource/rpm_package.rb
@@ -22,12 +22,10 @@ require 'chef/provider/package/rpm'
 class Chef
   class Resource
     class RpmPackage < Chef::Resource::Package
-
       provides :rpm_package, os: [ "linux", "aix" ]
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :rpm_package
         @allow_downgrade = false
       end
 
diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb
index 2b2aa02..3c39090 100644
--- a/lib/chef/resource/ruby.rb
+++ b/lib/chef/resource/ruby.rb
@@ -22,13 +22,10 @@ require 'chef/provider/script'
 class Chef
   class Resource
     class Ruby < Chef::Resource::Script
-
       def initialize(name, run_context=nil)
         super
-        @resource_name = :ruby
         @interpreter = "ruby"
       end
-
     end
   end
 end
diff --git a/lib/chef/resource/ruby_block.rb b/lib/chef/resource/ruby_block.rb
index a9cbf23..ae8e4cb 100644
--- a/lib/chef/resource/ruby_block.rb
+++ b/lib/chef/resource/ruby_block.rb
@@ -23,14 +23,13 @@ require 'chef/provider/ruby_block'
 class Chef
   class Resource
     class RubyBlock < Chef::Resource
+      default_action :run
+      allowed_actions :create, :run
 
       identity_attr :block_name
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :ruby_block
-        @action = "run"
-        @allowed_actions << :create << :run
         @block_name = name
       end
 
diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb
index 87c217b..85028c2 100644
--- a/lib/chef/resource/scm.rb
+++ b/lib/chef/resource/scm.rb
@@ -22,23 +22,22 @@ require 'chef/resource'
 class Chef
   class Resource
     class Scm < Chef::Resource
-
       identity_attr :destination
 
       state_attrs :revision
 
+      default_action :sync
+      allowed_actions :checkout, :export, :sync, :diff, :log
+
       def initialize(name, run_context=nil)
         super
         @destination = name
-        @resource_name = :scm
         @enable_submodules = false
         @enable_checkout = true
         @revision = "HEAD"
         @remote = "origin"
         @ssh_wrapper = nil
         @depth = nil
-        @allowed_actions.push(:checkout, :export, :sync, :diff, :log)
-        @action = [:sync]
         @checkout_branch = "deploy"
         @environment = nil
       end
diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb
index fd0fd5a..5081adf 100644
--- a/lib/chef/resource/script.rb
+++ b/lib/chef/resource/script.rb
@@ -23,13 +23,11 @@ require 'chef/provider/script'
 class Chef
   class Resource
     class Script < Chef::Resource::Execute
-
       # Chef-13: go back to using :name as the identity attr
       identity_attr :command
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :script
         # Chef-13: the command variable should be initialized to nil
         @command = name
         @code = nil
@@ -42,7 +40,7 @@ class Chef
         unless arg.nil?
           # Chef-13: change this to raise if the user is trying to set a value here
           Chef::Log.warn "Specifying command attribute on a script resource is a coding error, use the 'code' attribute, or the execute resource"
-          Chef::Log.warn "This attribute is deprecated and must be fixed or this code will fail on Chef-13"
+          Chef::Log.warn "This attribute is deprecated and must be fixed or this code will fail on Chef 13"
         end
         super
       end
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index 36df7c8..6d1b81f 100644
--- a/lib/chef/resource/service.rb
+++ b/lib/chef/resource/service.rb
@@ -1,7 +1,7 @@
 #
 # Author:: AJ Christensen (<aj at hjksolutions.com>)
 # Author:: Tyler Cloke (<tyler at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,14 +22,15 @@ require 'chef/resource'
 class Chef
   class Resource
     class Service < Chef::Resource
-
       identity_attr :service_name
 
       state_attrs :enabled, :running
 
+      default_action :nothing
+      allowed_actions :enable, :disable, :start, :stop, :restart, :reload
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :service
         @service_name = name
         @enabled = nil
         @running = nil
@@ -43,9 +44,8 @@ class Chef
         @init_command = nil
         @priority = nil
         @timeout = nil
-        @action = "nothing"
-        @supports = { :restart => false, :reload => false, :status => false }
-        @allowed_actions.push(:enable, :disable, :start, :stop, :restart, :reload)
+        @run_levels = nil
+        @supports = { :restart => nil, :reload => nil, :status => nil }
       end
 
       def service_name(arg=nil)
@@ -175,6 +175,13 @@ class Chef
         )
       end
 
+      def run_levels(arg=nil)
+        set_or_return(
+          :run_levels,
+          arg,
+          :kind_of => [ Array ] )
+      end
+
       def supports(args={})
         if args.is_a? Array
           args.each { |arg| @supports[arg] = true }
diff --git a/lib/chef/resource/smartos_package.rb b/lib/chef/resource/smartos_package.rb
index 99b3b95..b8bd940 100644
--- a/lib/chef/resource/smartos_package.rb
+++ b/lib/chef/resource/smartos_package.rb
@@ -22,16 +22,7 @@ require 'chef/provider/package/smartos'
 class Chef
   class Resource
     class SmartosPackage < Chef::Resource::Package
-
-      provides :smartos_package
       provides :package, os: "solaris2", platform_family: "smartos"
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :smartos_package
-      end
-
     end
   end
 end
-
diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb
index 94be469..a98fb8b 100644
--- a/lib/chef/resource/solaris_package.rb
+++ b/lib/chef/resource/solaris_package.rb
@@ -23,21 +23,8 @@ require 'chef/provider/package/solaris'
 class Chef
   class Resource
     class SolarisPackage < Chef::Resource::Package
-
-      provides :solaris_package
       provides :package, os: "solaris2", platform_family: "nexentacore"
-      provides :package, os: "solaris2", platform_family: "solaris2" do |node|
-        # on >= Solaris 11 we default to IPS packages instead
-        node[:platform_version].to_f <= 5.10
-      end
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :solaris_package
-      end
-
+      provides :package, os: "solaris2", platform_family: "solaris2", platform_version: "<= 5.10"
     end
   end
 end
-
-
diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb
index 3afbe0b..a6f4cb4 100644
--- a/lib/chef/resource/subversion.rb
+++ b/lib/chef/resource/subversion.rb
@@ -22,19 +22,23 @@ require "chef/resource/scm"
 class Chef
   class Resource
     class Subversion < Chef::Resource::Scm
+      allowed_actions :force_export
 
       def initialize(name, run_context=nil)
         super
         @svn_arguments = '--no-auth-cache'
         @svn_info_args = '--no-auth-cache'
-        @resource_name = :subversion
-        allowed_actions << :force_export
+        @svn_binary = nil
       end
 
       # Override exception to strip password if any, so it won't appear in logs and different Chef notifications
       def custom_exception_message(e)
         "#{self} (#{defined_at}) had an error: #{e.class.name}: #{svn_password ? e.message.gsub(svn_password, "[hidden_password]") : e.message}"
       end
+
+      def svn_binary(arg=nil)
+        set_or_return(:svn_binary, arg, :kind_of => [String])
+      end
     end
   end
 end
diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb
index 67a9e6a..5a7f7ef 100644
--- a/lib/chef/resource/template.rb
+++ b/lib/chef/resource/template.rb
@@ -27,15 +27,11 @@ class Chef
     class Template < Chef::Resource::File
       include Chef::Mixin::Securable
 
-      provides :template
-
       attr_reader :inline_helper_blocks
       attr_reader :inline_helper_modules
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :template
-        @action = "create"
         @source = "#{::File.basename(name)}.erb"
         @cookbook = nil
         @local = false
diff --git a/lib/chef/resource/timestamped_deploy.rb b/lib/chef/resource/timestamped_deploy.rb
index b2109db..344f8b0 100644
--- a/lib/chef/resource/timestamped_deploy.rb
+++ b/lib/chef/resource/timestamped_deploy.rb
@@ -21,10 +21,6 @@ class Chef
     # Convenience class for using the deploy resource with the timestamped
     # deployment strategy (provider)
     class TimestampedDeploy < Chef::Resource::Deploy
-      provides :timestamped_deploy
-      def initialize(*args, &block)
-        super(*args, &block)
-      end
     end
   end
 end
diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb
index 7d2ec25..b85b648 100644
--- a/lib/chef/resource/user.rb
+++ b/lib/chef/resource/user.rb
@@ -21,16 +21,15 @@ require 'chef/resource'
 class Chef
   class Resource
     class User < Chef::Resource
-
       identity_attr :username
 
       state_attrs :uid, :gid, :home
 
-      provides :user
+      default_action :create
+      allowed_actions :create, :remove, :modify, :manage, :lock, :unlock
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :user
         @username = name
         @comment = nil
         @uid = nil
@@ -42,14 +41,12 @@ class Chef
         @manage_home = false
         @force = false
         @non_unique = false
-        @action = :create
         @supports = {
           :manage_home => false,
           :non_unique => false
         }
         @iterations = 27855
         @salt = nil
-        @allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock)
       end
 
       def username(arg=nil)
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/resource/whyrun_safe_ruby_block.rb
index 6fa5383..f289f15 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/resource/whyrun_safe_ruby_block.rb
@@ -19,12 +19,6 @@
 class Chef
   class Resource
     class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :whyrun_safe_ruby_block
-      end
-
     end
   end
 end
diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb
index 16cfcf8..f11b7b8 100644
--- a/lib/chef/resource/windows_package.rb
+++ b/lib/chef/resource/windows_package.rb
@@ -16,6 +16,7 @@
 # limitations under the License.
 #
 
+require 'chef/mixin/uris'
 require 'chef/resource/package'
 require 'chef/provider/package/windows'
 require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
@@ -23,14 +24,15 @@ require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
 class Chef
   class Resource
     class WindowsPackage < Chef::Resource::Package
+      include Chef::Mixin::Uris
 
-      provides :package, os: "windows"
       provides :windows_package, os: "windows"
+      provides :package, os: "windows"
+
+      allowed_actions :install, :remove
 
       def initialize(name, run_context=nil)
         super
-        @allowed_actions.push(:install, :remove)
-        @resource_name = :windows_package
         @source ||= source(@package_name)
 
         # Unique to this resource
@@ -65,14 +67,34 @@ class Chef
       end
 
       def source(arg=nil)
-        if arg == nil && self.instance_variable_defined?(:@source) == true
+        if arg == nil
           @source
         else
           raise ArgumentError, "Bad type for WindowsPackage resource, use a String" unless arg.is_a?(String)
-          Chef::Log.debug("#{package_name}: sanitizing source path '#{arg}'")
-          @source = ::File.absolute_path(arg).gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)
+          if uri_scheme?(arg)
+            @source = arg
+          else
+            @source = Chef::Util::PathHelper.canonical_path(arg, false)
+          end
         end
       end
+
+      def checksum(arg=nil)
+        set_or_return(
+          :checksum,
+          arg,
+          :kind_of => [ String ]
+        )
+      end
+
+      def remote_file_attributes(arg=nil)
+        set_or_return(
+          :remote_file_attributes,
+          arg,
+          :kind_of => [ Hash ]
+        )
+      end
+
     end
   end
 end
diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb
index 6b0827b..2bbd01d 100644
--- a/lib/chef/resource/windows_script.rb
+++ b/lib/chef/resource/windows_script.rb
@@ -16,12 +16,14 @@
 # limitations under the License.
 #
 
+require 'chef/platform/query_helpers'
 require 'chef/resource/script'
 require 'chef/mixin/windows_architecture_helper'
 
 class Chef
   class Resource
     class WindowsScript < Chef::Resource::Script
+      # This is an abstract resource meant to be subclasses; thus no 'provides'
 
       set_guard_inherited_attributes(:architecture)
 
@@ -30,8 +32,8 @@ class Chef
       def initialize(name, run_context, resource_name, interpreter_command)
         super(name, run_context)
         @interpreter = interpreter_command
-        @resource_name = resource_name
-        @default_guard_interpreter = resource_name
+        @resource_name = resource_name if resource_name
+        @default_guard_interpreter = self.resource_name
       end
 
       include Chef::Mixin::WindowsArchitectureHelper
@@ -50,9 +52,12 @@ class Chef
       protected
 
       def assert_architecture_compatible!(desired_architecture)
-        if ! node_supports_windows_architecture?(node, desired_architecture)
+        if desired_architecture == :i386 && Chef::Platform.windows_nano_server?
           raise Chef::Exceptions::Win32ArchitectureIncorrect,
-          "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'"
+            "cannot execute script with requested architecture 'i386' on Windows Nano Server"
+        elsif ! node_supports_windows_architecture?(node, desired_architecture)
+          raise Chef::Exceptions::Win32ArchitectureIncorrect,
+            "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'"
         end
       end
     end
diff --git a/lib/chef/resource/windows_service.rb b/lib/chef/resource/windows_service.rb
index 8090adc..a776906 100644
--- a/lib/chef/resource/windows_service.rb
+++ b/lib/chef/resource/windows_service.rb
@@ -25,8 +25,10 @@ class Chef
       # Until #1773 is resolved, you need to manually specify the windows_service resource
       # to use action :configure_startup and attribute startup_type
 
-      provides :service, os: "windows"
       provides :windows_service, os: "windows"
+      provides :service, os: "windows"
+
+      allowed_actions :configure_startup
 
       identity_attr :service_name
 
@@ -34,8 +36,6 @@ class Chef
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :windows_service
-        @allowed_actions.push(:configure_startup)
         @startup_type = :automatic
         @run_as_user = ""
         @run_as_password = ""
diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb
index 8fbca9b..50ba13c 100644
--- a/lib/chef/resource/yum_package.rb
+++ b/lib/chef/resource/yum_package.rb
@@ -1,6 +1,6 @@
 #
 # Author:: AJ Christensen (<aj at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,15 +22,13 @@ require 'chef/provider/package/yum'
 class Chef
   class Resource
     class YumPackage < Chef::Resource::Package
-
-      provides :yum_package
       provides :package, os: "linux", platform_family: [ "rhel", "fedora" ]
 
       def initialize(name, run_context=nil)
         super
-        @resource_name = :yum_package
         @flush_cache = { :before => false, :after => false }
         @allow_downgrade = false
+        @yum_binary = nil
       end
 
       # Install a specific arch
@@ -38,7 +36,7 @@ class Chef
         set_or_return(
           :arch,
           arg,
-          :kind_of => [ String ]
+          :kind_of => [ String, Array ]
         )
       end
 
@@ -60,6 +58,14 @@ class Chef
         )
       end
 
+      def yum_binary(arg=nil)
+        set_or_return(
+          :yum_binary,
+          arg,
+          :kind_of => [ String ]
+        )
+      end
+
     end
   end
 end
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/resource/zypper_package.rb
similarity index 71%
copy from lib/chef/resource/whyrun_safe_ruby_block.rb
copy to lib/chef/resource/zypper_package.rb
index 6fa5383..f09a20e 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/resource/zypper_package.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Phil Dibowitz (<phild at fb.com>)
-# Copyright:: Copyright (c) 2013 Facebook
+# Author:: Joe Williams (<joe at joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,12 @@
 # limitations under the License.
 #
 
+require 'chef/resource/package'
+
 class Chef
   class Resource
-    class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :whyrun_safe_ruby_block
-      end
-
+    class ZypperPackage < Chef::Resource::Package
+      provides :package, platform_family: "suse"
     end
   end
 end
diff --git a/lib/chef/resource_builder.rb b/lib/chef/resource_builder.rb
index bb0962d..5b2ed4b 100644
--- a/lib/chef/resource_builder.rb
+++ b/lib/chef/resource_builder.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Lamont Granquist (<lamont at chef.io>)
-# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# Copyright:: Copyright (c) 2015-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -46,6 +46,9 @@ class Chef
       raise ArgumentError, "You must supply a name when declaring a #{type} resource" if name.nil?
 
       @resource = resource_class.new(name, run_context)
+      if resource.resource_name.nil?
+        raise Chef::Exceptions::InvalidResourceSpecification, "#{resource}.resource_name is `nil`!  Did you forget to put `provides :blah` or `resource_name :blah` in your resource class?"
+      end
       resource.source_line = created_at
       resource.declared_type = type
 
@@ -67,7 +70,14 @@ class Chef
       resource.params = params
 
       # Evaluate resource attribute DSL
-      resource.instance_eval(&block) if block_given?
+      if block_given?
+        resource.resource_initializing = true
+        begin
+          resource.instance_eval(&block)
+        ensure
+          resource.resource_initializing = false
+        end
+      end
 
       # emit a cloned resource warning if it is warranted
       if prior_resource
@@ -127,7 +137,7 @@ class Chef
       @prior_resource ||=
         begin
           key = "#{type}[#{name}]"
-          prior_resource = run_context.resource_collection.lookup(key)
+          run_context.resource_collection.lookup(key)
         rescue Chef::Exceptions::ResourceNotFound
           nil
         end
@@ -135,3 +145,7 @@ class Chef
 
   end
 end
+
+require 'chef/exceptions'
+require 'chef/resource'
+require 'chef/log'
diff --git a/lib/chef/resource_definition.rb b/lib/chef/resource_definition.rb
index 9d68441..cffabb6 100644
--- a/lib/chef/resource_definition.rb
+++ b/lib/chef/resource_definition.rb
@@ -50,6 +50,7 @@ class Chef
       else
         raise ArgumentError, "You must pass a block to a definition."
       end
+      Chef::DSL::Definitions.add_definition(name)
       true
     end
 
diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb
index 1816fc8..1175b0a 100644
--- a/lib/chef/resource_reporter.rb
+++ b/lib/chef/resource_reporter.rb
@@ -59,11 +59,11 @@ class Chef
       # attrs.
       def for_json
         as_hash = {}
-        as_hash["type"]   = new_resource.class.dsl_name
+        as_hash["type"]   = new_resource.resource_name.to_sym
         as_hash["name"]   = new_resource.name.to_s
         as_hash["id"]     = new_resource.identity.to_s
-        as_hash["after"]  = state(new_resource)
-        as_hash["before"] = current_resource ? state(current_resource) : {}
+        as_hash["after"]  = new_resource.state_for_resource_reporter
+        as_hash["before"] = current_resource ? current_resource.state_for_resource_reporter : {}
         as_hash["duration"] = (elapsed_time * 1000).to_i.to_s
         as_hash["delta"]  = new_resource.diff if new_resource.respond_to?("diff")
         as_hash["delta"]  = "" if as_hash["delta"].nil?
@@ -89,13 +89,6 @@ class Chef
       def success?
         !self.exception
       end
-
-      def state(r)
-        r.class.state_attrs.inject({}) do |state_attrs, attr_name|
-          state_attrs[attr_name] = r.send(attr_name)
-          state_attrs
-        end
-      end
     end # End class ResouceReport
 
     attr_reader :updated_resources
@@ -119,6 +112,7 @@ class Chef
       @exception = nil
       @rest_client = rest_client
       @error_descriptions = {}
+      @expanded_run_list = {}
     end
 
     def run_started(run_status)
@@ -220,10 +214,14 @@ class Chef
       # If we failed before we received the run_started callback, there's not much we can do
       # in terms of reporting
       if @run_status
-          post_reporting_data
+        post_reporting_data
       end
     end
 
+    def run_list_expanded(run_list_expansion)
+      @expanded_run_list = run_list_expansion
+    end
+
     def post_reporting_data
       if reporting_enabled?
         run_data = prepare_run_data
@@ -278,6 +276,7 @@ class Chef
       run_data["data"] = {}
       run_data["start_time"] = start_time.to_s
       run_data["end_time"] = end_time.to_s
+      run_data["expanded_run_list"] = Chef::JSONCompat.to_json(@expanded_run_list)
 
       if exception
         exception_data = {}
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
index ff9d7ae..67cf134 100644
--- a/lib/chef/resource_resolver.rb
+++ b/lib/chef/resource_resolver.rb
@@ -18,84 +18,169 @@
 
 require 'chef/exceptions'
 require 'chef/platform/resource_priority_map'
+require 'chef/mixin/convert_to_class_name'
 
 class Chef
   class ResourceResolver
+    #
+    # Resolve a resource by name.
+    #
+    # @param resource_name [Symbol] The resource DSL name (e.g. `:file`).
+    # @param node [Chef::Node] The node against which to resolve. `nil` causes
+    #   platform filters to be ignored.
+    #
+    def self.resolve(resource_name, node: nil, canonical: nil)
+      new(node, resource_name, canonical: canonical).resolve
+    end
+
+    #
+    # Resolve a list of all resources that implement the given DSL (in order of
+    # preference).
+    #
+    # @param resource_name [Symbol] The resource DSL name (e.g. `:file`).
+    # @param node [Chef::Node] The node against which to resolve. `nil` causes
+    #   platform filters to be ignored.
+    # @param canonical [Boolean] `true` or `false` to match canonical or
+    #   non-canonical values only. `nil` to ignore canonicality.
+    #
+    def self.list(resource_name, node: nil, canonical: nil)
+      new(node, resource_name, canonical: canonical).list
+    end
+
 
+    include Chef::Mixin::ConvertToClassName
+
+    # @api private
     attr_reader :node
-    attr_reader :resource
+    # @api private
+    attr_reader :resource_name
+    # @api private
+    def resource
+      Chef.log_deprecation("Chef::ResourceResolver.resource deprecated.  Use resource_name instead.")
+      resource_name
+    end
+    # @api private
     attr_reader :action
-
-    def initialize(node, resource)
+    # @api private
+    attr_reader :canonical
+
+    #
+    # Create a resolver.
+    #
+    # @param node [Chef::Node] The node against which to resolve. `nil` causes
+    #   platform filters to be ignored.
+    # @param resource_name [Symbol] The resource DSL name (e.g. `:file`).
+    # @param canonical [Boolean] `true` or `false` to match canonical or
+    #   non-canonical values only. `nil` to ignore canonicality.  Default: `nil`
+    #
+    # @api private use Chef::ResourceResolver.resolve or .list instead.
+    def initialize(node, resource_name, canonical: nil)
       @node = node
-      @resource = resource
+      @resource_name = resource_name.to_sym
+      @canonical = canonical
     end
 
-    # return a deterministically sorted list of Chef::Resource subclasses
-    def resources
-      @resources ||= Chef::Resource.descendants
+    # @api private use Chef::ResourceResolver.resolve instead.
+    def resolve
+      # log this so we know what resources will work for the generic resource on the node (early cut)
+      Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
+
+      handler = prioritized_handlers.first
+
+      if handler
+        Chef::Log.debug "Resource for #{resource_name} is #{handler}"
+      else
+        Chef::Log.debug "Dynamic resource resolver FAILED to resolve a resource for #{resource_name}"
+      end
+
+      handler
     end
 
-    def resolve
-      maybe_dynamic_resource_resolution(resource) ||
-        maybe_chef_platform_lookup(resource)
+    # @api private
+    def list
+      Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
+      prioritized_handlers
     end
 
-    # this cut looks at if the resource can handle the resource type on the node
-    def enabled_handlers
-      @enabled_handlers ||=
-        resources.select do |klass|
-          klass.provides?(node, resource)
-        end.sort {|a,b| a.to_s <=> b.to_s }
-      @enabled_handlers
+    #
+    # Whether this DSL is provided by the given resource_class.
+    #
+    # Does NOT call provides? on the resource (it is assumed this is being
+    # called *from* provides?).
+    #
+    # @api private
+    def provided_by?(resource_class)
+      potential_handlers.include?(resource_class)
+    end
+
+    #
+    # Whether the given handler attempts to provide the resource class at all.
+    #
+    # @api private
+    def self.includes_handler?(resource_name, resource_class)
+      handler_map.list(nil, resource_name).include?(resource_class)
     end
 
-    private
+    protected
 
-    # try dynamically finding a resource based on querying the resources to see what they support
-    def maybe_dynamic_resource_resolution(resource)
-      # log this so we know what resources will work for the generic resource on the node (early cut)
-      Chef::Log.debug "resources for generic #{resource} resource enabled on node include: #{enabled_handlers}"
-
-      # if none of the resources specifically support the resource, we still need to pick one of the resources that are
-      # enabled on the node to handle the why-run use case.
-      handlers = enabled_handlers
-
-      if handlers.count >= 2
-        # this magic stack ranks the resources by where they appear in the resource_priority_map
-        priority_list = [ get_priority_array(node, resource) ].flatten.compact
-        handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i }
-        if priority_list.index(handlers.first).nil?
-          # if we had more than one and we picked one with a precidence of infinity that means that the resource_priority_map
-          # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity.
-          Chef::Log.warn "Ambiguous resource precedence: #{handlers}, please use Chef.set_resource_priority_array to provide determinism"
-        end
-        handlers = [ handlers.first ]
-      end
+    def self.priority_map
+      Chef.resource_priority_map
+    end
 
-      Chef::Log.debug "resources that survived replacement include: #{handlers}"
+    def self.handler_map
+      Chef.resource_handler_map
+    end
 
-      raise Chef::Exceptions::AmbiguousResourceResolution.new(resource, handlers) if handlers.count >= 2
+    def priority_map
+      Chef.resource_priority_map
+    end
 
-      Chef::Log.debug "dynamic resource resolver FAILED to resolve a resouce" if handlers.empty?
+    def handler_map
+      Chef.resource_handler_map
+    end
 
-      return nil if handlers.empty?
+    # @api private
+    def potential_handlers
+      handler_map.list(node, resource_name, canonical: canonical).uniq
+    end
 
-      handlers[0]
+    def enabled_handlers
+      potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource_name) }
     end
 
-    # try the old static lookup of resources by mangling name to resource klass
-    def maybe_chef_platform_lookup(resource)
-      Chef::Resource.resource_matching_short_name(resource)
+    def prioritized_handlers
+      @prioritized_handlers ||= begin
+        enabled_handlers = self.enabled_handlers
+
+        prioritized = priority_map.list(node, resource_name, canonical: canonical).flatten(1)
+        prioritized &= enabled_handlers # Filter the priority map by the actual enabled handlers
+        prioritized |= enabled_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set)
+        prioritized
+      end
     end
 
-    # dep injection hooks
-    def get_priority_array(node, resource_name)
-      resource_priority_map.get_priority_array(node, resource_name)
+    def overrode_provides?(handler)
+      handler.method(:provides?).owner != Chef::Resource.method(:provides?).owner
     end
 
-    def resource_priority_map
-      Chef::Platform::ResourcePriorityMap.instance
+    module Deprecated
+      # return a deterministically sorted list of Chef::Resource subclasses
+      def resources
+        Chef::Resource.sorted_descendants
+      end
+
+      def enabled_handlers
+        handlers = super
+        if handlers.empty?
+          handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) }
+          handlers.each do |handler|
+            Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!")
+            Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+          end
+        end
+        handlers
+      end
     end
+    prepend Deprecated
   end
 end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 40b12a7..6db0fc9 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -43,6 +43,7 @@ require 'chef/resource/group'
 require 'chef/resource/http_request'
 require 'chef/resource/homebrew_package'
 require 'chef/resource/ifconfig'
+require 'chef/resource/ksh'
 require 'chef/resource/link'
 require 'chef/resource/log'
 require 'chef/resource/macports_package'
@@ -80,10 +81,4 @@ require 'chef/resource/windows_package'
 require 'chef/resource/yum_package'
 require 'chef/resource/lwrp_base'
 require 'chef/resource/bff_package'
-
-begin
-  # Optional resources chef_node, chef_client, machine, machine_image, etc.
-  require 'cheffish'
-  require 'chef/provisioning'
-rescue LoadError
-end
+require 'chef/resource/zypper_package'
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index 2612714..4106a01 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -64,6 +64,7 @@ class Chef
       options = options.dup
       options[:client_name] = client_name
       options[:signing_key_filename] = signing_key_filename
+
       super(url, options)
 
       @decompressor = Decompressor.new(options)
@@ -165,7 +166,7 @@ class Chef
     def retriable_http_request(method, url, req_body, headers)
       rest_request = Chef::HTTP::HTTPRequest.new(method, url, req_body, headers)
 
-      Chef::Log.debug("Sending HTTP Request via #{method} to #{url.host}:#{url.port}#{rest_request.path}")
+      Chef::Log.debug("Sending HTTP request via #{method} to #{url.host}:#{url.port}#{rest_request.path}")
 
       retrying_http_errors(url) do
         yield rest_request
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 4f0215b..6e02f47 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -25,117 +25,263 @@ require 'chef/log'
 require 'chef/recipe'
 require 'chef/run_context/cookbook_compiler'
 require 'chef/event_dispatch/events_output_stream'
+require 'forwardable'
 
 class Chef
 
   # == Chef::RunContext
   # Value object that loads and tracks the context of a Chef run
   class RunContext
+    #
+    # Global state
+    #
 
-    # Chef::Node object for this run
+    #
+    # The node for this run
+    #
+    # @return [Chef::Node]
+    #
     attr_reader :node
 
-    # Chef::CookbookCollection for this run
+    #
+    # The set of cookbooks involved in this run
+    #
+    # @return [Chef::CookbookCollection]
+    #
     attr_reader :cookbook_collection
 
+    #
     # Resource Definitions for this run. Populated when the files in
     # +definitions/+ are evaluated (this is triggered by #load).
+    #
+    # @return [Array[Chef::ResourceDefinition]]
+    #
     attr_reader :definitions
 
-    ###
-    # These need to be settable so deploy can run a resource_collection
-    # independent of any cookbooks via +recipe_eval+
+    #
+    # Event dispatcher for this run.
+    #
+    # @return [Chef::EventDispatch::Dispatcher]
+    #
+    attr_reader :events
+
+    #
+    # Hash of factoids for a reboot request.
+    #
+    # @return [Hash]
+    #
+    attr_accessor :reboot_info
 
-    # The Chef::ResourceCollection for this run. Populated by evaluating
-    # recipes, which is triggered by #load. (See also: CookbookCompiler)
-    attr_accessor :resource_collection
+    #
+    # Scoped state
+    #
 
+    #
+    # The parent run context.
+    #
+    # @return [Chef::RunContext] The parent run context, or `nil` if this is the
+    #   root context.
+    #
+    attr_reader :parent_run_context
+
+    #
+    # The collection of resources intended to be converged (and able to be
+    # notified).
+    #
+    # @return [Chef::ResourceCollection]
+    #
+    # @see CookbookCompiler
+    #
+    attr_reader :resource_collection
+
+    #
     # The list of control groups to execute during the audit phase
-    attr_accessor :audits
+    #
+    attr_reader :audits
+
+    #
+    # Notification handling
+    #
 
+    #
+    # A Hash containing the before notifications triggered by resources
+    # during the converge phase of the chef run.
+    #
+    # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+    #   <notifying resource name> => <list of notifications it sent>
+    #
+    attr_reader :before_notification_collection
+
+    #
     # A Hash containing the immediate notifications triggered by resources
     # during the converge phase of the chef run.
-    attr_accessor :immediate_notification_collection
+    #
+    # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+    #   <notifying resource name> => <list of notifications it sent>
+    #
+    attr_reader :immediate_notification_collection
 
+    #
     # A Hash containing the delayed (end of run) notifications triggered by
     # resources during the converge phase of the chef run.
-    attr_accessor :delayed_notification_collection
-
-    # Event dispatcher for this run.
-    attr_reader :events
-
-    # Hash of factoids for a reboot request.
-    attr_reader :reboot_info
+    #
+    # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+    #   <notifying resource name> => <list of notifications it sent>
+    #
+    attr_reader :delayed_notification_collection
 
     # Creates a new Chef::RunContext object and populates its fields. This object gets
     # used by the Chef Server to generate a fully compiled recipe list for a node.
     #
-    # === Returns
-    # object<Chef::RunContext>:: Duh. :)
+    # @param node [Chef::Node] The node to run against.
+    # @param cookbook_collection [Chef::CookbookCollection] The cookbooks
+    #   involved in this run.
+    # @param events [EventDispatch::Dispatcher] The event dispatcher for this
+    #   run.
+    #
     def initialize(node, cookbook_collection, events)
       @node = node
       @cookbook_collection = cookbook_collection
-      @resource_collection = Chef::ResourceCollection.new
-      @audits = {}
-      @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
-      @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
-      @definitions = Hash.new
-      @loaded_recipes = {}
-      @loaded_attributes = {}
       @events = events
-      @reboot_info = {}
 
-      @node.run_context = self
+      node.run_context = self
+      node.set_cookbook_attribute
+
+      @definitions = Hash.new
+      @loaded_recipes_hash = {}
+      @loaded_attributes_hash = {}
+      @reboot_info = {}
       @cookbook_compiler = nil
+
+      initialize_child_state
     end
 
-    # Triggers the compile phase of the chef run. Implemented by
-    # Chef::RunContext::CookbookCompiler
+    #
+    # Triggers the compile phase of the chef run.
+    #
+    # @param run_list_expansion [Chef::RunList::RunListExpansion] The run list.
+    # @see Chef::RunContext::CookbookCompiler
+    #
     def load(run_list_expansion)
       @cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
-      @cookbook_compiler.compile
+      cookbook_compiler.compile
     end
 
-    # Adds an immediate notification to the
-    # +immediate_notification_collection+. The notification should be a
-    # Chef::Resource::Notification or duck type.
+    #
+    # Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext
+    #
+    def initialize_child_state
+      @audits = {}
+      @resource_collection = Chef::ResourceCollection.new
+      @before_notification_collection = Hash.new {|h,k| h[k] = []}
+      @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
+      @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
+    end
+
+    #
+    # Adds an before notification to the +before_notification_collection+.
+    #
+    # @param [Chef::Resource::Notification] The notification to add.
+    #
+    def notifies_before(notification)
+      nr = notification.notifying_resource
+      if nr.instance_of?(Chef::Resource)
+        before_notification_collection[nr.name] << notification
+      else
+        before_notification_collection[nr.declared_key] << notification
+      end
+    end
+
+    #
+    # Adds an immediate notification to the +immediate_notification_collection+.
+    #
+    # @param [Chef::Resource::Notification] The notification to add.
+    #
     def notifies_immediately(notification)
       nr = notification.notifying_resource
       if nr.instance_of?(Chef::Resource)
-        @immediate_notification_collection[nr.name] << notification
+        immediate_notification_collection[nr.name] << notification
       else
-        @immediate_notification_collection[nr.declared_key] << notification
+        immediate_notification_collection[nr.declared_key] << notification
       end
     end
 
-    # Adds a delayed notification to the +delayed_notification_collection+. The
-    # notification should be a Chef::Resource::Notification or duck type.
+    #
+    # Adds a delayed notification to the +delayed_notification_collection+.
+    #
+    # @param [Chef::Resource::Notification] The notification to add.
+    #
     def notifies_delayed(notification)
       nr = notification.notifying_resource
       if nr.instance_of?(Chef::Resource)
-        @delayed_notification_collection[nr.name] << notification
+        delayed_notification_collection[nr.name] << notification
       else
-        @delayed_notification_collection[nr.declared_key] << notification
+        delayed_notification_collection[nr.declared_key] << notification
       end
     end
 
+    #
+    # Get the list of before notifications sent by the given resource.
+    #
+    # TODO seriously, this is actually wrong.  resource.name is not unique,
+    # you need the type as well.
+    #
+    # @return [Array[Notification]]
+    #
+    def before_notifications(resource)
+      if resource.instance_of?(Chef::Resource)
+        return before_notification_collection[resource.name]
+      else
+        return before_notification_collection[resource.declared_key]
+      end
+    end
+
+    #
+    # Get the list of immediate notifications sent by the given resource.
+    #
+    # TODO seriously, this is actually wrong.  resource.name is not unique,
+    # you need the type as well.
+    #
+    # @return [Array[Notification]]
+    #
     def immediate_notifications(resource)
       if resource.instance_of?(Chef::Resource)
-        return @immediate_notification_collection[resource.name]
+        return immediate_notification_collection[resource.name]
       else
-        return @immediate_notification_collection[resource.declared_key]
+        return immediate_notification_collection[resource.declared_key]
       end
     end
 
+    #
+    # Get the list of delayed (end of run) notifications sent by the given
+    # resource.
+    #
+    # TODO seriously, this is actually wrong.  resource.name is not unique,
+    # you need the type as well.
+    #
+    # @return [Array[Notification]]
+    #
     def delayed_notifications(resource)
       if resource.instance_of?(Chef::Resource)
-        return @delayed_notification_collection[resource.name]
+        return delayed_notification_collection[resource.name]
       else
-        return @delayed_notification_collection[resource.declared_key]
+        return delayed_notification_collection[resource.declared_key]
       end
     end
 
+    #
+    # Cookbook and recipe loading
+    #
+
+    #
     # Evaluates the recipes +recipe_names+. Used by DSL::IncludeRecipe
+    #
+    # @param recipe_names [Array[String]] The list of recipe names (e.g.
+    #   'my_cookbook' or 'my_cookbook::my_resource').
+    # @param current_cookbook The cookbook we are currently running in.
+    #
+    # @see DSL::IncludeRecipe#include_recipe
+    #
     def include_recipe(*recipe_names, current_cookbook: nil)
       result_recipes = Array.new
       recipe_names.flatten.each do |recipe_name|
@@ -146,9 +292,23 @@ class Chef
       result_recipes
     end
 
+    #
     # Evaluates the recipe +recipe_name+. Used by DSL::IncludeRecipe
+    #
+    # TODO I am sort of confused why we have both this and include_recipe ...
+    #      I don't see anything different beyond accepting and returning an
+    #      array of recipes.
+    #
+    # @param recipe_names [Array[String]] The recipe name (e.g 'my_cookbook' or
+    #   'my_cookbook::my_resource').
+    # @param current_cookbook The cookbook we are currently running in.
+    #
+    # @return A truthy value if the load occurred; `false` if already loaded.
+    #
+    # @see DSL::IncludeRecipe#load_recipe
+    #
     def load_recipe(recipe_name, current_cookbook: nil)
-      Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
+      Chef::Log.debug("Loading recipe #{recipe_name} via include_recipe")
 
       cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name, current_cookbook: current_cookbook)
 
@@ -174,19 +334,39 @@ ERROR_MESSAGE
       end
     end
 
+    #
+    # Load the given recipe from a filename.
+    #
+    # @param recipe_file [String] The recipe filename.
+    #
+    # @return [Chef::Recipe] The loaded recipe.
+    #
+    # @raise [Chef::Exceptions::RecipeNotFound] If the file does not exist.
+    #
     def load_recipe_file(recipe_file)
       if !File.exist?(recipe_file)
         raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}"
       end
 
-      Chef::Log.debug("Loading Recipe File #{recipe_file}")
+      Chef::Log.debug("Loading recipe file #{recipe_file}")
       recipe = Chef::Recipe.new('@recipe_files', recipe_file, self)
       recipe.from_file(recipe_file)
       recipe
     end
 
-    # Looks up an attribute file given the +cookbook_name+ and
-    # +attr_file_name+. Used by DSL::IncludeAttribute
+    #
+    # Look up an attribute filename.
+    #
+    # @param cookbook_name [String] The cookbook name of the attribute file.
+    # @param attr_file_name [String] The attribute file's name (not path).
+    #
+    # @return [String] The filename.
+    #
+    # @see DSL::IncludeAttribute#include_attribute
+    #
+    # @raise [Chef::Exceptions::CookbookNotFound] If the cookbook could not be found.
+    # @raise [Chef::Exceptions::AttributeNotFound] If the attribute file could not be found.
+    #
     def resolve_attribute(cookbook_name, attr_file_name)
       cookbook = cookbook_collection[cookbook_name]
       raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook
@@ -197,76 +377,152 @@ ERROR_MESSAGE
       attribute_filename
     end
 
-    # An Array of all recipes that have been loaded. This is stored internally
-    # as a Hash, so ordering is predictable.
     #
-    # Recipe names are given in fully qualified form, e.g., the recipe "nginx"
-    # will be given as "nginx::default"
+    # A list of all recipes that have been loaded.
+    #
+    # This is stored internally as a Hash, so ordering is predictable.
+    #
+    # TODO is the above statement true in a 1.9+ ruby world?  Is it relevant?
+    #
+    # @return [Array[String]] A list of recipes in fully qualified form, e.g.
+    #   the recipe "nginx" will be given as "nginx::default".
+    #
+    # @see #loaded_recipe? To determine if a particular recipe has been loaded.
     #
-    # To determine if a particular recipe has been loaded, use #loaded_recipe?
     def loaded_recipes
-      @loaded_recipes.keys
+      loaded_recipes_hash.keys
     end
 
-    # An Array of all attributes files that have been loaded. Stored internally
-    # using a Hash, so order is predictable.
     #
-    # Attribute file names are given in fully qualified form, e.g.,
-    # "nginx::default" instead of "nginx".
+    # A list of all attributes files that have been loaded.
+    #
+    # Stored internally using a Hash, so order is predictable.
+    #
+    # TODO is the above statement true in a 1.9+ ruby world?  Is it relevant?
+    #
+    # @return [Array[String]] A list of attribute file names in fully qualified
+    #   form, e.g. the "nginx" will be given as "nginx::default".
+    #
     def loaded_attributes
-      @loaded_attributes.keys
+      loaded_attributes_hash.keys
     end
 
+    #
+    # Find out if a given recipe has been loaded.
+    #
+    # @param cookbook [String] Cookbook name.
+    # @param recipe [String] Recipe name.
+    #
+    # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+    #
     def loaded_fully_qualified_recipe?(cookbook, recipe)
-      @loaded_recipes.has_key?("#{cookbook}::#{recipe}")
+      loaded_recipes_hash.has_key?("#{cookbook}::#{recipe}")
     end
 
-    # Returns true if +recipe+ has been loaded, false otherwise. Default recipe
-    # names are expanded, so `loaded_recipe?("nginx")` and
-    # `loaded_recipe?("nginx::default")` are valid and give identical results.
+    #
+    # Find out if a given recipe has been loaded.
+    #
+    # @param recipe [String] Recipe name.  "nginx" and "nginx::default" yield
+    #   the same results.
+    #
+    # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+    #
     def loaded_recipe?(recipe)
       cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe)
       loaded_fully_qualified_recipe?(cookbook, recipe_name)
     end
 
+    #
+    # Mark a given recipe as having been loaded.
+    #
+    # @param cookbook [String] Cookbook name.
+    # @param recipe [String] Recipe name.
+    #
+    def loaded_recipe(cookbook, recipe)
+      loaded_recipes_hash["#{cookbook}::#{recipe}"] = true
+    end
+
+    #
+    # Find out if a given attribute file has been loaded.
+    #
+    # @param cookbook [String] Cookbook name.
+    # @param attribute_file [String] Attribute file name.
+    #
+    # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+    #
     def loaded_fully_qualified_attribute?(cookbook, attribute_file)
-      @loaded_attributes.has_key?("#{cookbook}::#{attribute_file}")
+      loaded_attributes_hash.has_key?("#{cookbook}::#{attribute_file}")
     end
 
+    #
+    # Mark a given attribute file as having been loaded.
+    #
+    # @param cookbook [String] Cookbook name.
+    # @param attribute_file [String] Attribute file name.
+    #
     def loaded_attribute(cookbook, attribute_file)
-      @loaded_attributes["#{cookbook}::#{attribute_file}"] = true
+      loaded_attributes_hash["#{cookbook}::#{attribute_file}"] = true
     end
 
     ##
     # Cookbook File Introspection
 
+    #
+    # Find out if the cookbook has the given template.
+    #
+    # @param cookbook [String] Cookbook name.
+    # @param template_name [String] Template name.
+    #
+    # @return [Boolean] `true` if the template is in the cookbook, `false`
+    #   otherwise.
+    # @see Chef::CookbookVersion#has_template_for_node?
+    #
     def has_template_in_cookbook?(cookbook, template_name)
       cookbook = cookbook_collection[cookbook]
       cookbook.has_template_for_node?(node, template_name)
     end
 
+    #
+    # Find out if the cookbook has the given file.
+    #
+    # @param cookbook [String] Cookbook name.
+    # @param cb_file_name [String] File name.
+    #
+    # @return [Boolean] `true` if the file is in the cookbook, `false`
+    #   otherwise.
+    # @see Chef::CookbookVersion#has_cookbook_file_for_node?
+    #
     def has_cookbook_file_in_cookbook?(cookbook, cb_file_name)
       cookbook = cookbook_collection[cookbook]
       cookbook.has_cookbook_file_for_node?(node, cb_file_name)
     end
 
-    # Delegates to CookbookCompiler#unreachable_cookbook?
-    # Used to raise an error when attempting to load a recipe belonging to a
-    # cookbook that is not in the dependency graph. See also: CHEF-4367
+    #
+    # Find out whether the given cookbook is in the cookbook dependency graph.
+    #
+    # @param cookbook_name [String] Cookbook name.
+    #
+    # @return [Boolean] `true` if the cookbook is reachable, `false` otherwise.
+    #
+    # @see Chef::CookbookCompiler#unreachable_cookbook?
     def unreachable_cookbook?(cookbook_name)
-      @cookbook_compiler.unreachable_cookbook?(cookbook_name)
+      cookbook_compiler.unreachable_cookbook?(cookbook_name)
     end
 
+    #
     # Open a stream object that can be printed into and will dispatch to events
     #
-    # == Arguments
-    # options is a hash with these possible options:
-    # - name: a string that identifies the stream to the user. Preferably short.
+    # @param name [String] The name of the stream.
+    # @param options [Hash] Other options for the stream.
+    #
+    # @return [EventDispatch::EventsOutputStream] The created stream.
     #
-    # Pass a block and the stream will be yielded to it, and close on its own
-    # at the end of the block.
-    def open_stream(options = {})
-      stream = EventDispatch::EventsOutputStream.new(events, options)
+    # @yield If a block is passed, it will be run and the stream will be closed
+    #   afterwards.
+    # @yieldparam stream [EventDispatch::EventsOutputStream] The created stream.
+    #
+    def open_stream(name: nil, **options)
+      stream = EventDispatch::EventsOutputStream.new(events, name: name, **options)
       if block_given?
         begin
           yield stream
@@ -279,31 +535,138 @@ ERROR_MESSAGE
     end
 
     # there are options for how to handle multiple calls to these functions:
-    # 1. first call always wins (never change @reboot_info once set).
-    # 2. last call always wins (happily change @reboot_info whenever).
+    # 1. first call always wins (never change reboot_info once set).
+    # 2. last call always wins (happily change reboot_info whenever).
     # 3. raise an exception on the first conflict.
     # 4. disable reboot after this run if anyone ever calls :cancel.
     # 5. raise an exception on any second call.
     # 6. ?
     def request_reboot(reboot_info)
-      Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to #{reboot_info.inspect}"
+      Chef::Log::info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
       @reboot_info = reboot_info
     end
 
     def cancel_reboot
-      Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to {}"
+      Chef::Log::info "Changing reboot status from #{reboot_info.inspect} to {}"
       @reboot_info = {}
     end
 
     def reboot_requested?
-      @reboot_info.size > 0
+      reboot_info.size > 0
+    end
+
+    #
+    # Create a child RunContext.
+    #
+    def create_child
+      ChildRunContext.new(self)
     end
 
-    private
+    # @api private
+    attr_writer :resource_collection
 
-    def loaded_recipe(cookbook, recipe)
-      @loaded_recipes["#{cookbook}::#{recipe}"] = true
+    protected
+
+    attr_reader :cookbook_compiler
+    attr_reader :loaded_attributes_hash
+    attr_reader :loaded_recipes_hash
+
+    module Deprecated
+      ###
+      # These need to be settable so deploy can run a resource_collection
+      # independent of any cookbooks via +recipe_eval+
+      def audits=(value)
+        Chef.log_deprecation("Setting run_context.audits will be removed in a future Chef.  Use run_context.create_child to create a new RunContext instead.")
+        @audits = value
+      end
+
+      def immediate_notification_collection=(value)
+        Chef.log_deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef.  Use run_context.create_child to create a new RunContext instead.")
+        @immediate_notification_collection = value
+      end
+
+      def delayed_notification_collection=(value)
+        Chef.log_deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef.  Use run_context.create_child to create a new RunContext instead.")
+        @delayed_notification_collection = value
+      end
     end
+    prepend Deprecated
+
+
+    #
+    # A child run context.  Delegates all root context calls to its parent.
+    #
+    # @api private
+    #
+    class ChildRunContext < RunContext
+      extend Forwardable
+      def_delegators :parent_run_context, *%w(
+        cancel_reboot
+        config
+        cookbook_collection
+        cookbook_compiler
+        definitions
+        events
+        has_cookbook_file_in_cookbook?
+        has_template_in_cookbook?
+        load
+        loaded_attribute
+        loaded_attributes
+        loaded_attributes_hash
+        loaded_fully_qualified_attribute?
+        loaded_fully_qualified_recipe?
+        loaded_recipe
+        loaded_recipe?
+        loaded_recipes
+        loaded_recipes_hash
+        node
+        open_stream
+        reboot_info
+        reboot_info=
+        reboot_requested?
+        request_reboot
+        resolve_attribute
+        unreachable_cookbook?
+      )
+
+      def initialize(parent_run_context)
+        @parent_run_context = parent_run_context
+
+        # We don't call super, because we don't bother initializing stuff we're
+        # going to delegate to the parent anyway.  Just initialize things that
+        # every instance needs.
+        initialize_child_state
+      end
 
+      CHILD_STATE = %w(
+        audits
+        audits=
+        create_child
+        delayed_notification_collection
+        delayed_notification_collection=
+        delayed_notifications
+        immediate_notification_collection
+        immediate_notification_collection=
+        immediate_notifications
+        before_notification_collection
+        before_notifications
+        include_recipe
+        initialize_child_state
+        load_recipe
+        load_recipe_file
+        notifies_before
+        notifies_immediately
+        notifies_delayed
+        parent_run_context
+        resource_collection
+        resource_collection=
+      ).map { |x| x.to_sym }
+
+      # Verify that we didn't miss any methods
+      missing_methods = superclass.instance_methods(false) - instance_methods(false) - CHILD_STATE
+      if !missing_methods.empty?
+        raise "ERROR: not all methods of RunContext accounted for in ChildRunContext! All methods must be marked as child methods with CHILD_STATE or delegated to the parent_run_context. Missing #{missing_methods.join(", ")}."
+      end
+    end
   end
 end
diff --git a/lib/chef/run_list/run_list_expansion.rb b/lib/chef/run_list/run_list_expansion.rb
index 46b45f1..64e4326 100644
--- a/lib/chef/run_list/run_list_expansion.rb
+++ b/lib/chef/run_list/run_list_expansion.rb
@@ -22,6 +22,7 @@ require 'chef/mixin/deep_merge'
 
 require 'chef/role'
 require 'chef/rest'
+require 'chef/json_compat'
 
 class Chef
   class RunList
@@ -54,6 +55,13 @@ class Chef
       # * Duplicate roles are not shown.
       attr_reader :run_list_trace
 
+      # Like run list trace but instead of saving the entries as strings it saves their objects
+      # The to_json method uses this list to construct json.
+      attr_reader :better_run_list_trace
+
+      attr_reader :all_missing_roles
+      attr_reader :role_errors
+
       def initialize(environment, run_list_items, source=nil)
         @environment = environment
         @missing_roles_with_including_role = Array.new
@@ -68,6 +76,9 @@ class Chef
 
         @applied_roles = {}
         @run_list_trace = Hash.new {|h, key| h[key] = [] }
+        @better_run_list_trace = Hash.new {|h, key| h[key] = [] }
+        @all_missing_roles = {}
+        @role_errors = {}
       end
 
       # Did we find any errors (expanding roles)?
@@ -124,6 +135,7 @@ class Chef
       def role_not_found(name, included_by)
         Chef::Log.error("Role #{name} (included by '#{included_by}') is in the runlist but does not exist. Skipping expand.")
         @missing_roles_with_including_role << [name, included_by]
+        @all_missing_roles[name] = true
         nil
       end
 
@@ -131,6 +143,15 @@ class Chef
         @missing_roles_with_including_role.map {|item| item.first }
       end
 
+      def to_json(*a)
+        Chef::JSONCompat.to_json(to_hash, *a)
+      end
+
+      def to_hash
+        seen_items = {:recipe => {}, :role => {}}
+        {:id => @environment, :run_list => convert_run_list_trace('top level', seen_items)}
+      end
+
       private
 
       # these methods modifies internal state based on arguments, so hide it.
@@ -140,8 +161,10 @@ class Chef
       end
 
       def expand_run_list_items(items, included_by="top level")
+
         if entry = items.shift
           @run_list_trace[included_by.to_s] << entry.to_s
+          @better_run_list_trace[included_by.to_s] <<  entry
 
           case entry.type
           when :recipe
@@ -156,8 +179,26 @@ class Chef
         end
       end
 
+      # Recursive helper to decode the non-nested hash form back into a tree
+      def convert_run_list_trace(base, seen_items)
+        @better_run_list_trace[base].map do |item|
+          skipped = seen_items[item.type][item.name]
+          seen_items[item.type][item.name] = true
+          case item.type
+            when :recipe
+                {:type => 'recipe', :name => item.name, :version => item.version, :skipped => !!skipped}
+            when :role
+              error = @role_errors[item.name]
+              missing = @all_missing_roles[item.name]
+              {:type => :role, :name => item.name, :children => (missing || error || skipped) ? [] : convert_run_list_trace(item.to_s, seen_items),
+               :missing => missing, :error => error, :skipped => skipped}
+          end
+        end
+      end
+
     end
 
+
     # Expand a run list from disk. Suitable for chef-solo
     class RunListExpansionFromDisk < RunListExpansion
 
@@ -184,8 +225,14 @@ class Chef
         else
           raise
         end
+      rescue Exception => e
+        @role_errors[name] = e.to_s
+        raise
       end
+
     end
 
   end
 end
+
+
diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb
index 0eefded..803156a 100644
--- a/lib/chef/run_list/versioned_recipe_list.rb
+++ b/lib/chef/run_list/versioned_recipe_list.rb
@@ -63,6 +63,40 @@ class Chef
           end
         end
       end
+
+      # Get an array of strings of the fully-qualified recipe names (with ::default appended) and
+      # with the versions in "NAME at VERSION" format.
+      #
+      # @return [Array] Array of strings with fully-qualified recipe names
+      def with_fully_qualified_names_and_version_constraints
+        self.map do |recipe_name|
+          qualified_recipe = if recipe_name.include?('::')
+            recipe_name
+          else
+            "#{recipe_name}::default"
+          end
+
+          version = @versions[recipe_name]
+          qualified_recipe = "#{qualified_recipe}@#{version}" if version
+
+          qualified_recipe
+        end
+      end
+
+      # Get an array of strings of both fully-qualified and unexpanded recipe names
+      # in response to chef/chef#3767
+      # Chef-13 will revert to the behaviour of just including the fully-qualified name
+      #
+      # @return [Array] Array of strings with fully-qualified and unexpanded recipe names
+      def with_duplicate_names
+        self.map do |recipe_name|
+          if recipe_name.include?('::')
+            recipe_name
+          else
+            [recipe_name, "#{recipe_name}::default"]
+          end
+        end.flatten
+      end
     end
   end
 end
diff --git a/lib/chef/run_lock.rb b/lib/chef/run_lock.rb
index cefe637..9e0952b 100644
--- a/lib/chef/run_lock.rb
+++ b/lib/chef/run_lock.rb
@@ -87,27 +87,8 @@ class Chef
     # Either acquire() or test() methods should be called in order to
     # get the ownership of run_lock.
     def test
-      # ensure the runlock_file path exists
-      create_path(File.dirname(runlock_file))
-      @runlock = File.open(runlock_file,'a+')
-
-      if Chef::Platform.windows?
-        acquire_win32_mutex
-      else
-        # If we support FD_CLOEXEC, then use it.
-        # NB: ruby-2.0.0-p195 sets FD_CLOEXEC by default, but not
-        # ruby-1.8.7/1.9.3
-        if Fcntl.const_defined?('F_SETFD') && Fcntl.const_defined?('FD_CLOEXEC')
-          runlock.fcntl(Fcntl::F_SETFD, runlock.fcntl(Fcntl::F_GETFD, 0) | Fcntl::FD_CLOEXEC)
-        end
-        # Flock will return 0 if it can acquire the lock otherwise it
-        # will return false
-        if runlock.flock(File::LOCK_NB|File::LOCK_EX) == 0
-          true
-        else
-          false
-        end
-      end
+      create_lock
+      acquire_lock
     end
 
     #
@@ -147,6 +128,34 @@ class Chef
       end
     end
 
+    # @api private solely for race condition tests
+    def create_lock
+      # ensure the runlock_file path exists
+      create_path(File.dirname(runlock_file))
+      @runlock = File.open(runlock_file,'a+')
+    end
+
+    # @api private solely for race condition tests
+    def acquire_lock
+      if Chef::Platform.windows?
+        acquire_win32_mutex
+      else
+        # If we support FD_CLOEXEC, then use it.
+        # NB: ruby-2.0.0-p195 sets FD_CLOEXEC by default, but not
+        # ruby-1.8.7/1.9.3
+        if Fcntl.const_defined?('F_SETFD') && Fcntl.const_defined?('FD_CLOEXEC')
+          runlock.fcntl(Fcntl::F_SETFD, runlock.fcntl(Fcntl::F_GETFD, 0) | Fcntl::FD_CLOEXEC)
+        end
+        # Flock will return 0 if it can acquire the lock otherwise it
+        # will return false
+        if runlock.flock(File::LOCK_NB|File::LOCK_EX) == 0
+          true
+        else
+          false
+        end
+      end
+    end
+
     private
 
     def reset
diff --git a/lib/chef/run_status.rb b/lib/chef/run_status.rb
index 0f18142..ce8ff29 100644
--- a/lib/chef/run_status.rb
+++ b/lib/chef/run_status.rb
@@ -39,15 +39,13 @@ class Chef::RunStatus
 
   attr_accessor :run_id
 
+  attr_accessor :node
+
   def initialize(node, events)
     @node = node
     @events = events
   end
 
-  def node
-    @node
-  end
-
   # sets +start_time+ to the current time.
   def start_clock
     @start_time = Time.now
diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb
index 6125fe5..61b68e2 100644
--- a/lib/chef/runner.rb
+++ b/lib/chef/runner.rb
@@ -46,6 +46,31 @@ class Chef
     # Determine the appropriate provider for the given resource, then
     # execute it.
     def run_action(resource, action, notification_type=nil, notifying_resource=nil)
+
+      # If there are any before notifications, why-run the resource
+      # and notify anyone who needs notifying
+      # TODO cheffish has a bug where it passes itself instead of the run_context to us, so doesn't have before_notifications. Fix there, update dependency requirement, and remove this if statement.
+      before_notifications = run_context.before_notifications(resource) if run_context.respond_to?(:before_notifications)
+      if before_notifications && !before_notifications.empty?
+        whyrun_before = Chef::Config[:why_run]
+        begin
+          Chef::Config[:why_run] = true
+          Chef::Log.info("#{resource} running why-run #{action} action to support before action")
+          resource.run_action(action, notification_type, notifying_resource)
+        ensure
+          Chef::Config[:why_run] = whyrun_before
+        end
+
+        if resource.updated_by_last_action?
+          before_notifications.each do |notification|
+            Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (before)")
+            run_action(notification.resource, notification.action, :before, resource)
+          end
+        end
+
+      end
+
+      # Actually run the action for realsies
       resource.run_action(action, notification_type, notifying_resource)
 
       # Execute any immediate and queue up any delayed notifications
diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb
index 6469a18..658af87 100644
--- a/lib/chef/search/query.rb
+++ b/lib/chef/search/query.rb
@@ -88,8 +88,21 @@ WARNDEP
 
         if block
           response["rows"].each { |row| block.call(row) if row }
-          unless (response["start"] + response["rows"].length) >= response["total"]
-            args_h[:start] = response["start"] + response["rows"].length
+          #
+          # args_h[:rows] and args_h[:start] are the page size and
+          # start position requested of the search index backing the
+          # search API.
+          #
+          # The response may contain fewer rows than arg_h[:rows] if
+          # the page of index results included deleted nodes which
+          # have been filtered from the returned data. In this case,
+          # we still want to start the next page at start +
+          # args_h[:rows] to avoid asking the search backend for
+          # overlapping pages (which could result in duplicates).
+          #
+          next_start = response["start"] + (args_h[:rows] || response["rows"].length)
+          unless next_start >= response["total"]
+            args_h[:start] = next_start
             search(type, query, args_h, &block)
           end
           true
@@ -99,6 +112,7 @@ WARNDEP
       end
 
       private
+
       def validate_type(t)
         unless t.kind_of?(String) || t.kind_of?(Symbol)
           msg = "Invalid search object type #{t.inspect} (#{t.class}), must be a String or Symbol." +
diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb
index ec4a864..764296f 100644
--- a/lib/chef/server_api.rb
+++ b/lib/chef/server_api.rb
@@ -42,3 +42,5 @@ class Chef
     use Chef::HTTP::RemoteRequestID
   end
 end
+
+require 'chef/config'
diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb
index ee4fe78..3a68785 100644
--- a/lib/chef/shell.rb
+++ b/lib/chef/shell.rb
@@ -110,7 +110,7 @@ module Shell
 
       conf.prompt_c       = "chef#{leader(m)} > "
       conf.return_format  = " => %s \n"
-      conf.prompt_i       = "chef#{leader(m)} > "
+      conf.prompt_i       = "chef#{leader(m)} (#{Chef::VERSION})> "
       conf.prompt_n       = "chef#{leader(m)} ?> "
       conf.prompt_s       = "chef#{leader(m)}%l> "
       conf.use_tracer     = false
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index 42fa6b5..31ebeda 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -21,7 +21,19 @@ require 'chef/mixin/from_file'
 require 'chef/mash'
 require 'chef/json_compat'
 require 'chef/search/query'
+require 'chef/server_api'
 
+# TODO
+# DEPRECATION NOTE
+# This class will be replaced by Chef::UserV1 in Chef 13. It is the code to support the User object
+# corrosponding to the Open Source Chef Server 11 and only still exists to support
+# users still on OSC 11.
+#
+# Chef::UserV1 now supports Chef Server 12 and will be moved to this namespace in Chef 13.
+#
+# New development should occur in Chef::UserV1.
+# This file and corrosponding osc_user knife files
+# should be removed once client support for Open Source Chef Server 11 expires.
 class Chef
   class User
 
@@ -36,6 +48,10 @@ class Chef
       @admin = false
     end
 
+    def chef_rest_v0
+      @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
+    end
+
     def name(arg=nil)
       set_or_return(:name, arg,
                     :regex => /^[a-z0-9\-_]+$/)
@@ -77,13 +93,13 @@ class Chef
     end
 
     def destroy
-      Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("users/#{@name}")
+      chef_rest_v0.delete("users/#{@name}")
     end
 
     def create
       payload = {:name => self.name, :admin => self.admin, :password => self.password }
       payload[:public_key] = public_key if public_key
-      new_user =Chef::REST.new(Chef::Config[:chef_server_url]).post_rest("users", payload)
+      new_user = chef_rest_v0.post("users", payload)
       Chef::User.from_hash(self.to_hash.merge(new_user))
     end
 
@@ -91,7 +107,7 @@ class Chef
       payload = {:name => name, :admin => admin}
       payload[:private_key] = new_key if new_key
       payload[:password] = password if password
-      updated_user = Chef::REST.new(Chef::Config[:chef_server_url]).put_rest("users/#{name}", payload)
+      updated_user = chef_rest_v0.put("users/#{name}", payload)
       Chef::User.from_hash(self.to_hash.merge(updated_user))
     end
 
@@ -108,8 +124,7 @@ class Chef
     end
 
     def reregister
-      r = Chef::REST.new(Chef::Config[:chef_server_url])
-      reregistered_self = r.put_rest("users/#{name}", { :name => name, :admin => admin, :private_key => true })
+      reregistered_self = chef_rest_v0.put("users/#{name}", { :name => name, :admin => admin, :private_key => true })
       private_key(reregistered_self["private_key"])
       self
     end
@@ -144,7 +159,7 @@ class Chef
     end
 
     def self.list(inflate=false)
-      response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users')
+      response =  Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get('users')
       users = if response.is_a?(Array)
         transform_ohc_list_response(response) # OHC/OPC
       else
@@ -161,7 +176,7 @@ class Chef
     end
 
     def self.load(name)
-      response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("users/#{name}")
+      response =  Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get("users/#{name}")
       Chef::User.from_hash(response)
     end
 
diff --git a/lib/chef/user_v1.rb b/lib/chef/user_v1.rb
new file mode 100644
index 0000000..31cb057
--- /dev/null
+++ b/lib/chef/user_v1.rb
@@ -0,0 +1,335 @@
+#
+# Author:: Steven Danna (steve at opscode.com)
+# Copyright:: Copyright 2012 Opscode, 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/config'
+require 'chef/mixin/params_validate'
+require 'chef/mixin/from_file'
+require 'chef/mash'
+require 'chef/json_compat'
+require 'chef/search/query'
+require 'chef/mixin/api_version_request_handling'
+require 'chef/exceptions'
+require 'chef/server_api'
+
+# OSC 11 BACKWARDS COMPATIBILITY NOTE (remove after OSC 11 support ends)
+#
+# In general, Chef::UserV1 is no longer expected to support Open Source Chef 11 Server requests.
+# The object that handles those requests remain in the Chef::User namespace.
+# This code will be moved to the Chef::User namespace as of Chef 13.
+#
+# Exception: self.list is backwards compatible with OSC 11
+class Chef
+  class UserV1
+
+    include Chef::Mixin::FromFile
+    include Chef::Mixin::ParamsValidate
+    include Chef::Mixin::ApiVersionRequestHandling
+
+    SUPPORTED_API_VERSIONS = [0,1]
+
+    def initialize
+      @username = nil
+      @display_name = nil
+      @first_name = nil
+      @middle_name = nil
+      @last_name = nil
+      @email = nil
+      @password = nil
+      @public_key = nil
+      @private_key = nil
+      @create_key = nil
+      @password = nil
+    end
+
+    def chef_root_rest_v0
+      @chef_root_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "0"})
+    end
+
+    def chef_root_rest_v1
+      @chef_root_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "1"})
+    end
+
+    def username(arg=nil)
+      set_or_return(:username, arg,
+                    :regex => /^[a-z0-9\-_]+$/)
+    end
+
+    def display_name(arg=nil)
+      set_or_return(:display_name,
+                    arg, :kind_of => String)
+    end
+
+    def first_name(arg=nil)
+      set_or_return(:first_name,
+                    arg, :kind_of => String)
+    end
+
+    def middle_name(arg=nil)
+      set_or_return(:middle_name,
+                    arg, :kind_of => String)
+    end
+
+    def last_name(arg=nil)
+      set_or_return(:last_name,
+                    arg, :kind_of => String)
+    end
+
+    def email(arg=nil)
+      set_or_return(:email,
+                    arg, :kind_of => String)
+    end
+
+    def password(arg=nil)
+      set_or_return(:password,
+                    arg, :kind_of => String)
+    end
+
+    def create_key(arg=nil)
+      set_or_return(:create_key, arg,
+                    :kind_of => [TrueClass, FalseClass])
+    end
+
+    def public_key(arg=nil)
+      set_or_return(:public_key,
+                    arg, :kind_of => String)
+    end
+
+    def private_key(arg=nil)
+      set_or_return(:private_key,
+                    arg, :kind_of => String)
+    end
+
+    def password(arg=nil)
+      set_or_return(:password,
+                    arg, :kind_of => String)
+    end
+
+    def to_hash
+      result = {
+        "username" => @username
+      }
+      result["display_name"] = @display_name unless @display_name.nil?
+      result["first_name"] = @first_name unless @first_name.nil?
+      result["middle_name"] = @middle_name unless @middle_name.nil?
+      result["last_name"] = @last_name unless @last_name.nil?
+      result["email"] = @email unless @email.nil?
+      result["password"] = @password unless @password.nil?
+      result["public_key"] = @public_key unless @public_key.nil?
+      result["private_key"] = @private_key unless @private_key.nil?
+      result["create_key"] = @create_key unless @create_key.nil?
+      result
+    end
+
+    def to_json(*a)
+      Chef::JSONCompat.to_json(to_hash, *a)
+    end
+
+    def destroy
+      # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
+      Chef::REST.new(Chef::Config[:chef_server_url]).delete("users/#{@username}")
+    end
+
+    def create
+      # try v1, fail back to v0 if v1 not supported
+      begin
+        payload = {
+          :username => @username,
+          :display_name => @display_name,
+          :first_name => @first_name,
+          :last_name => @last_name,
+          :email => @email,
+          :password => @password
+        }
+        payload[:public_key] = @public_key unless @public_key.nil?
+        payload[:create_key] = @create_key unless @create_key.nil?
+        payload[:middle_name] = @middle_name unless @middle_name.nil?
+        raise Chef::Exceptions::InvalidUserAttribute, "You cannot set both public_key and create_key for create." if !@create_key.nil? && !@public_key.nil?
+        new_user = chef_root_rest_v1.post("users", payload)
+
+        # get the private_key out of the chef_key hash if it exists
+        if new_user['chef_key']
+          if new_user['chef_key']['private_key']
+            new_user['private_key'] = new_user['chef_key']['private_key']
+          end
+          new_user['public_key'] = new_user['chef_key']['public_key']
+          new_user.delete('chef_key')
+        end
+      rescue Net::HTTPServerException => e
+        # rescue API V0 if 406 and the server supports V0
+        supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+        raise e unless supported_versions && supported_versions.include?(0)
+        payload = {
+          :username => @username,
+          :display_name => @display_name,
+          :first_name => @first_name,
+          :last_name => @last_name,
+          :email => @email,
+          :password => @password
+        }
+        payload[:middle_name] = @middle_name unless @middle_name.nil?
+        payload[:public_key] = @public_key unless @public_key.nil?
+        # under API V0, the server will create a key pair if public_key isn't passed
+        new_user = chef_root_rest_v0.post("users", payload)
+      end
+
+      Chef::UserV1.from_hash(self.to_hash.merge(new_user))
+    end
+
+    def update(new_key=false)
+      begin
+        payload = {:username => username}
+        payload[:display_name] = display_name unless display_name.nil?
+        payload[:first_name] = first_name unless first_name.nil?
+        payload[:middle_name] = middle_name unless middle_name.nil?
+        payload[:last_name] = last_name unless last_name.nil?
+        payload[:email] = email unless email.nil?
+        payload[:password] = password unless password.nil?
+
+        # API V1 will fail if these key fields are defined, and try V0 below if relevant 400 is returned
+        payload[:public_key] = public_key unless public_key.nil?
+        payload[:private_key] = new_key if new_key
+
+        updated_user = chef_root_rest_v1.put("users/#{username}", payload)
+      rescue Net::HTTPServerException => e
+        if e.response.code == "400"
+          # if a 400 is returned but the error message matches the error related to private / public key fields, try V0
+          # else, raise the 400
+          error = Chef::JSONCompat.from_json(e.response.body)["error"].first
+          error_match = /Since Server API v1, all keys must be updated via the keys endpoint/.match(error)
+          if error_match.nil?
+            raise e
+          end
+        else # for other types of errors, test for API versioning errors right away
+          supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+          raise e unless supported_versions && supported_versions.include?(0)
+        end
+        updated_user = chef_root_rest_v0.put("users/#{username}", payload)
+      end
+      Chef::UserV1.from_hash(self.to_hash.merge(updated_user))
+    end
+
+    def save(new_key=false)
+      begin
+        create
+      rescue Net::HTTPServerException => e
+        if e.response.code == "409"
+          update(new_key)
+        else
+          raise e
+        end
+      end
+    end
+
+    # Note: remove after API v0 no longer supported by client (and knife command).
+    def reregister
+      begin
+        payload = self.to_hash.merge({"private_key" => true})
+        reregistered_self = chef_root_rest_v0.put("users/#{username}", payload)
+        private_key(reregistered_self["private_key"])
+      # only V0 supported for reregister
+      rescue Net::HTTPServerException => e
+        # if there was a 406 related to versioning, give error explaining that
+        # only API version 0 is supported for reregister command
+        if e.response.code == "406" && e.response["x-ops-server-api-version"]
+          version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
+          min_version = version_header["min_version"]
+          max_version = version_header["max_version"]
+          error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
+          raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
+        else
+          raise e
+        end
+      end
+      self
+    end
+
+    def to_s
+      "user[#{@username}]"
+    end
+
+    # Class Methods
+
+    def self.from_hash(user_hash)
+      user = Chef::UserV1.new
+      user.username user_hash['username']
+      user.display_name user_hash['display_name'] if user_hash.key?('display_name')
+      user.first_name user_hash['first_name'] if user_hash.key?('first_name')
+      user.middle_name user_hash['middle_name'] if user_hash.key?('middle_name')
+      user.last_name user_hash['last_name'] if user_hash.key?('last_name')
+      user.email user_hash['email'] if user_hash.key?('email')
+      user.password user_hash['password'] if user_hash.key?('password')
+      user.public_key user_hash['public_key'] if user_hash.key?('public_key')
+      user.private_key user_hash['private_key'] if user_hash.key?('private_key')
+      user.create_key user_hash['create_key'] if user_hash.key?('create_key')
+      user
+    end
+
+    def self.from_json(json)
+      Chef::UserV1.from_hash(Chef::JSONCompat.from_json(json))
+    end
+
+    class << self
+      alias_method :json_create, :from_json
+    end
+
+    def self.list(inflate=false)
+      response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users')
+      users = if response.is_a?(Array)
+                # EC 11 / CS 12 V0, V1
+                #   GET /organizations/<org>/users
+                transform_list_response(response)
+              else
+                # OSC 11
+                #  GET /users
+                # EC 11 / CS 12 V0, V1
+                #  GET /users
+                response # OSC
+              end
+
+      if inflate
+        users.inject({}) do |user_map, (name, _url)|
+          user_map[name] = Chef::UserV1.load(name)
+          user_map
+        end
+      else
+        users
+      end
+    end
+
+    def self.load(username)
+      # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
+      response = Chef::REST.new(Chef::Config[:chef_server_url]).get("users/#{username}")
+      Chef::UserV1.from_hash(response)
+    end
+
+    # Gross.  Transforms an API response in the form of:
+    # [ { "user" => { "username" => USERNAME }}, ...]
+    # into the form
+    # { "USERNAME" => "URI" }
+    def self.transform_list_response(response)
+      new_response = Hash.new
+      response.each do |u|
+        name = u['user']['username']
+        new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}"
+      end
+      new_response
+    end
+
+    private_class_method :transform_list_response
+
+  end
+end
diff --git a/lib/chef/util/backup.rb b/lib/chef/util/backup.rb
index 0cc009c..6c95ced 100644
--- a/lib/chef/util/backup.rb
+++ b/lib/chef/util/backup.rb
@@ -78,8 +78,16 @@ class Chef
         Chef::Log.info("#{@new_resource} removed backup at #{backup_file}")
       end
 
+      def unsorted_backup_files
+        # If you replace this with Dir[], you will probably break Windows.
+        fn = Regexp.escape(::File.basename(path))
+        Dir.entries(::File.dirname(backup_path)).select do |f|
+          !!(f =~ /\A#{fn}.chef-[0-9.]*\B/)
+        end.map {|f| ::File.join(::File.dirname(backup_path), f)}
+      end
+
       def sorted_backup_files
-        Dir[Chef::Util::PathHelper.escape_glob(prefix, ".#{path}") + ".chef-*"].sort { |a,b| b <=> a }
+        unsorted_backup_files.sort { |a,b| b <=> a }
       end
     end
   end
diff --git a/lib/chef/util/diff.rb b/lib/chef/util/diff.rb
index c2dc6e0..b8336b5 100644
--- a/lib/chef/util/diff.rb
+++ b/lib/chef/util/diff.rb
@@ -64,7 +64,7 @@ class Chef
       def use_tempfile_if_missing(file)
         tempfile = nil
         unless File.exists?(file)
-          Chef::Log.debug("file #{file} does not exist to diff against, using empty tempfile")
+          Chef::Log.debug("File #{file} does not exist to diff against, using empty tempfile")
           tempfile = Tempfile.new("chef-diff")
           file = tempfile.path
         end
@@ -139,7 +139,7 @@ class Chef
         return "(new content is binary, diff output suppressed)" if is_binary?(new_file)
 
         begin
-          Chef::Log.debug("running: diff -u #{old_file} #{new_file}")
+          Chef::Log.debug("Running: diff -u #{old_file} #{new_file}")
           diff_str = udiff(old_file, new_file)
 
         rescue Exception => e
diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb
index 66c2e3f..9ebc931 100644
--- a/lib/chef/util/path_helper.rb
+++ b/lib/chef/util/path_helper.rb
@@ -16,212 +16,10 @@
 # limitations under the License.
 #
 
+require 'chef-config/path_helper'
+
 class Chef
   class Util
-    class PathHelper
-      # Maximum characters in a standard Windows path (260 including drive letter and NUL)
-      WIN_MAX_PATH = 259
-
-      def self.dirname(path)
-        if Chef::Platform.windows?
-          # Find the first slash, not counting trailing slashes
-          end_slash = path.size
-          loop do
-            slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1)
-            if !slash
-              return end_slash == path.size ? '.' : path_separator
-            elsif slash == end_slash - 1
-              end_slash = slash
-            else
-              return path[0..slash-1]
-            end
-          end
-        else
-          ::File.dirname(path)
-        end
-      end
-
-      BACKSLASH = '\\'.freeze
-
-      def self.path_separator
-        if Chef::Platform.windows?
-          File::ALT_SEPARATOR || BACKSLASH
-        else
-          File::SEPARATOR
-        end
-      end
-
-      def self.join(*args)
-        args.flatten.inject do |joined_path, component|
-          # Joined path ends with /
-          joined_path = joined_path.sub(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+$/, '')
-          component = component.sub(/^[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+/, '')
-          joined_path += "#{path_separator}#{component}"
-        end
-      end
-
-      def self.validate_path(path)
-        if Chef::Platform.windows?
-          unless printable?(path)
-            msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings."
-            Chef::Log.error(msg)
-            raise Chef::Exceptions::ValidationFailed, msg
-          end
-
-          if windows_max_length_exceeded?(path)
-            Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'")
-            path.insert(0, "\\\\?\\")
-          end
-        end
-
-        path
-      end
-
-      def self.windows_max_length_exceeded?(path)
-        # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API
-        # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
-        unless path =~ /^\\\\?\\/
-          if path.length > WIN_MAX_PATH
-            return true
-          end
-        end
-
-        false
-      end
-
-      def self.printable?(string)
-        # returns true if string is free of non-printable characters (escape sequences)
-        # this returns false for whitespace escape sequences as well, e.g. \n\t
-        if string =~ /[^[:print:]]/
-          false
-        else
-          true
-        end
-      end
-
-      # Produces a comparable path.
-      def self.canonical_path(path, add_prefix=true)
-        # First remove extra separators and resolve any relative paths
-        abs_path = File.absolute_path(path)
-
-        if Chef::Platform.windows?
-          # Add the \\?\ API prefix on Windows unless add_prefix is false
-          # Downcase on Windows where paths are still case-insensitive
-          abs_path.gsub!(::File::SEPARATOR, path_separator)
-          if add_prefix && abs_path !~ /^\\\\?\\/
-            abs_path.insert(0, "\\\\?\\")
-          end
-
-          abs_path.downcase!
-        end
-
-        abs_path
-      end
-
-      def self.cleanpath(path)
-        path = Pathname.new(path).cleanpath.to_s
-        # ensure all forward slashes are backslashes
-        if Chef::Platform.windows?
-          path = path.gsub(File::SEPARATOR, path_separator)
-        end
-        path
-      end
-
-      def self.paths_eql?(path1, path2)
-        canonical_path(path1) == canonical_path(path2)
-      end
-
-      # Paths which may contain glob-reserved characters need
-      # to be escaped before globbing can be done.
-      # http://stackoverflow.com/questions/14127343
-      def self.escape_glob(*parts)
-        path = cleanpath(join(*parts))
-        path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x }
-      end
-
-      def self.relative_path_from(from, to)
-        pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from)))
-      end
-
-      # Retrieves the "home directory" of the current user while trying to ascertain the existence
-      # of said directory.  The path returned uses / for all separators (the ruby standard format).
-      # If the home directory doesn't exist or an error is otherwise encountered, nil is returned.
-      # 
-      # If a set of path elements is provided, they are appended as-is to the home path if the
-      # homepath exists. 
-      # 
-      # If an optional block is provided, the joined path is passed to that block if the home path is
-      # valid and the result of the block is returned instead.
-      #
-      # Home-path discovery is performed once.  If a path is discovered, that value is memoized so
-      # that subsequent calls to home_dir don't bounce around.
-      #
-      # See self.all_homes.
-      def self.home(*args)
-        @@home_dir ||= self.all_homes { |p| break p }
-        if @@home_dir
-          path = File.join(@@home_dir, *args)
-          block_given? ? (yield path) : path
-        end
-      end
-
-      # See self.home.  This method performs a similar operation except that it yields all the different
-      # possible values of 'HOME' that one could have on this platform.  Hence, on windows, if
-      # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice.
-      # This method goes out and checks the existence of each location at the time of the call.
-      #
-      # The return is a list of all the returned values from each block invocation or a list of paths
-      # if no block is provided.
-      def self.all_homes(*args)
-        paths = []
-        if Chef::Platform.windows?
-          # By default, Ruby uses the the following environment variables to determine Dir.home:
-          # HOME
-          # HOMEDRIVE HOMEPATH
-          # USERPROFILE
-          # Ruby only checks to see if the variable is specified - not if the directory actually exists.
-          # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive)
-          # while USERPROFILE points to the location where the user application settings and profile are stored.  HOME
-          # is not defined as an environment variable (usually).  If the home path actually uses UNC, then the prefix is
-          # HOMESHARE instead of HOMEDRIVE.
-          #
-          # We instead walk down the following and only include paths that actually exist.
-          # HOME
-          # HOMEDRIVE HOMEPATH
-          # HOMESHARE HOMEPATH
-          # USERPROFILE
-
-          paths << ENV['HOME']
-          paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
-          paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH']
-          paths << ENV['USERPROFILE']
-        end
-        paths << Dir.home if ENV['HOME']
-
-        # Depending on what environment variables we're using, the slashes can go in any which way.
-        # Just change them all to / to keep things consistent.
-        # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on
-        # the particular brand of kool-aid you consume.  This code assumes that \ and / are both
-        # path separators on any system being used.
-        paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path }
-
-        # Filter out duplicate paths and paths that don't exist.
-        valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) }
-        valid_paths = valid_paths.uniq
-
-        # Join all optional path elements at the end.
-        # If a block is provided, invoke it - otherwise just return what we've got.
-        joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) }
-        if block_given?
-          joined_paths.each { |p| yield p }
-        else
-          joined_paths
-        end
-      end
-    end
+    PathHelper = ChefConfig::PathHelper
   end
 end
-
-# Break a require loop when require chef/util/path_helper
-require 'chef/platform'
-require 'chef/exceptions'
diff --git a/lib/chef/util/powershell/ps_credential.rb b/lib/chef/util/powershell/ps_credential.rb
index 01f8c27..2fc0650 100644
--- a/lib/chef/util/powershell/ps_credential.rb
+++ b/lib/chef/util/powershell/ps_credential.rb
@@ -29,6 +29,9 @@ class Chef::Util::Powershell
       "New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
     end
 
+    alias to_s to_psobject
+    alias to_text to_psobject
+
     private
 
     def encrypt(str)
diff --git a/lib/chef/util/windows.rb b/lib/chef/util/windows.rb
index 777fe4a..7d29a67 100644
--- a/lib/chef/util/windows.rb
+++ b/lib/chef/util/windows.rb
@@ -15,42 +15,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-#requires: gem install windows-pr
-require 'windows/api'
-require 'windows/error'
-require 'windows/handle'
-require 'windows/unicode'
-require 'windows/msvcrt/buffer'
-require 'windows/msvcrt/string'
-require 'windows/network/management'
 
 class Chef
   class Util
     class Windows
-      protected
-
-      include ::Windows::Error
-      include ::Windows::Unicode
-      include ::Windows::MSVCRT::Buffer
-      include ::Windows::MSVCRT::String
-      include ::Windows::Network::Management
-
-      PTR_SIZE = 4 #XXX 64-bit
-
-      def lpwstr_to_s(buffer, offset)
-        str = 0.chr * (256 * 2) #XXX unhardcode this length (*2 for WCHAR)
-        wcscpy str, buffer[offset*PTR_SIZE,PTR_SIZE].unpack('L')[0]
-        wide_to_multi str
-      end
-
-      def dword_to_i(buffer, offset)
-        buffer[offset*PTR_SIZE,PTR_SIZE].unpack('i')[0] || 0
-      end
-
-      #return pointer for use with pack('L')
-      def str_to_ptr(v)
-        [v].pack('p*').unpack('L')[0]
-      end
     end
   end
 end
diff --git a/lib/chef/util/windows/net_group.rb b/lib/chef/util/windows/net_group.rb
index 924bd39..2085747 100644
--- a/lib/chef/util/windows/net_group.rb
+++ b/lib/chef/util/windows/net_group.rb
@@ -1,106 +1,85 @@
-#
-# Author:: Doug MacEachern (<dougm at vmware.com>)
-# Copyright:: Copyright (c) 2010 VMware, 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/util/windows'
-
-#wrapper around a subset of the NetGroup* APIs.
-#nothing Chef specific, but not complete enough to be its own gem, so util for now.
-class Chef::Util::Windows::NetGroup < Chef::Util::Windows
-
-  private
-
-  def pack_str(s)
-    [str_to_ptr(s)].pack('L')
-  end
-
-  def modify_members(members, func)
-    buffer = 0.chr * (members.size * PTR_SIZE)
-    members.each_with_index do |member,offset|
-      buffer[offset*PTR_SIZE,PTR_SIZE] = pack_str(multi_to_wide(member))
-    end
-    rc = func.call(nil, @name, 3, buffer, members.size)
-    if rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
-    end
-  end
-
-  public
-
-  def initialize(groupname)
-    @name = multi_to_wide(groupname)
-  end
-
-  def local_get_members
-    group_members = []
-    handle = 0.chr * PTR_SIZE
-    rc = ERROR_MORE_DATA
-
-    while rc == ERROR_MORE_DATA
-      ptr   = 0.chr * PTR_SIZE
-      nread = 0.chr * PTR_SIZE
-      total = 0.chr * PTR_SIZE
-
-      rc = NetLocalGroupGetMembers.call(nil, @name, 0, ptr, -1,
-                                        nread, total, handle)
-      if (rc == NERR_Success) || (rc == ERROR_MORE_DATA)
-        ptr = ptr.unpack('L')[0]
-        nread = nread.unpack('i')[0]
-        members = 0.chr * (nread * PTR_SIZE ) #nread * sizeof(LOCALGROUP_MEMBERS_INFO_0)
-        memcpy(members, ptr, members.size)
-
-        # 1 pointer field in LOCALGROUP_MEMBERS_INFO_0, offset 0 is lgrmi0_sid
-        nread.times do |i|
-          sid_address = members[i * PTR_SIZE, PTR_SIZE].unpack('L')[0]
-          sid_ptr = FFI::Pointer.new(sid_address)
-          member_sid = Chef::ReservedNames::Win32::Security::SID.new(sid_ptr)
-          group_members << member_sid.to_s
-        end
-        NetApiBufferFree(ptr)
-      else
-        raise ArgumentError, get_last_error(rc)
-      end
-    end
-    group_members
-  end
-
-  def local_add
-    rc = NetLocalGroupAdd.call(nil, 0, pack_str(@name), nil)
-    if rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
-    end
-  end
-
-  def local_set_members(members)
-    modify_members(members, NetLocalGroupSetMembers)
-  end
-
-  def local_add_members(members)
-    modify_members(members, NetLocalGroupAddMembers)
-  end
-
-  def local_delete_members(members)
-    modify_members(members, NetLocalGroupDelMembers)
-  end
-
-  def local_delete
-    rc = NetLocalGroupDel.call(nil, @name)
-    if rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
-    end
-  end
-end
+#
+# Author:: Doug MacEachern (<dougm at vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, 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/util/windows'
+require 'chef/win32/net'
+
+#wrapper around a subset of the NetGroup* APIs.
+class Chef::Util::Windows::NetGroup
+
+  private
+
+  def groupname
+    @groupname
+  end
+
+  public
+
+  def initialize(groupname)
+    @groupname = groupname
+  end
+
+  def local_get_members
+    begin
+      Chef::ReservedNames::Win32::NetUser::net_local_group_get_members(nil, groupname)
+    rescue Chef::Exceptions::Win32NetAPIError => e
+      raise ArgumentError, e.msg
+    end
+  end
+
+  def local_add
+    begin
+      Chef::ReservedNames::Win32::NetUser::net_local_group_add(nil, groupname)
+    rescue Chef::Exceptions::Win32NetAPIError => e
+      raise ArgumentError, e.msg
+    end
+  end
+
+  def local_set_members(members)
+    begin
+      Chef::ReservedNames::Win32::NetUser::net_local_group_set_members(nil, groupname, members)
+    rescue Chef::Exceptions::Win32NetAPIError => e
+      raise ArgumentError, e.msg
+    end
+  end
+
+  def local_add_members(members)
+    begin
+      Chef::ReservedNames::Win32::NetUser::net_local_group_add_members(nil, groupname, members)
+    rescue Chef::Exceptions::Win32NetAPIError => e
+      raise ArgumentError, e.msg
+    end
+  end
+
+  def local_delete_members(members)
+    begin
+      Chef::ReservedNames::Win32::NetUser::net_local_group_del_members(nil, groupname, members)
+    rescue Chef::Exceptions::Win32NetAPIError => e
+      raise ArgumentError, e.msg
+    end
+
+  end
+
+  def local_delete
+    begin
+      Chef::ReservedNames::Win32::NetUser::net_local_group_del(nil, groupname)
+    rescue Chef::Exceptions::Win32NetAPIError => e
+      raise ArgumentError, e.msg
+    end
+  end
+end
diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb
index 62d7e16..b94576e 100644
--- a/lib/chef/util/windows/net_use.rb
+++ b/lib/chef/util/windows/net_use.rb
@@ -21,61 +21,18 @@
 #see also cmd.exe: net use /?
 
 require 'chef/util/windows'
+require 'chef/win32/net'
 
 class Chef::Util::Windows::NetUse < Chef::Util::Windows
-
-  private
-
-  USE_NOFORCE = 0
-  USE_FORCE = 1
-  USE_LOTS_OF_FORCE = 2 #every windows API should support this flag
-
-  USE_INFO_2 = [
-    [:local, nil],
-    [:remote, nil],
-    [:password, nil],
-    [:status, 0],
-    [:asg_type, 0],
-    [:refcount, 0],
-    [:usecount, 0],
-    [:username, nil],
-    [:domainname, nil]
-  ]
-
-  USE_INFO_2_TEMPLATE =
-    USE_INFO_2.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join
-
-  SIZEOF_USE_INFO_2 = #sizeof(USE_INFO_2)
-    USE_INFO_2.inject(0) do |sum, item|
-      sum + (item[1].class == Fixnum ? 4 : PTR_SIZE)
-    end
-
-  def use_info_2(args)
-    USE_INFO_2.collect { |field|
-      args.include?(field[0]) ? args[field[0]] : field[1]
-    }
-  end
-
-  def use_info_2_pack(use)
-    use.collect { |v|
-      v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v))
-    }.pack(USE_INFO_2_TEMPLATE)
+  def initialize(localname)
+    @use_name = localname
   end
 
-  def use_info_2_unpack(buffer)
-    use = Hash.new
-    USE_INFO_2.each_with_index do |field,offset|
-      use[field[0]] = field[1].class == Fixnum ?
-      dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset)
+  def to_ui2_struct(use_info)
+    use_info.inject({}) do |memo, (k,v)|
+      memo["ui2_#{k}".to_sym] = v
+      memo
     end
-    use
-  end
-
-  public
-
-  def initialize(localname)
-    @localname = localname
-    @name = multi_to_wide(localname)
   end
 
   def add(args)
@@ -84,38 +41,45 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows
       args = Hash.new
       args[:remote] = remote
     end
-    args[:local] ||= @localname
-    use = use_info_2(args)
-    buffer = use_info_2_pack(use)
-    rc = NetUseAdd.call(nil, 2, buffer, nil)
-    if rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
+    args[:local] ||= use_name
+    ui2_hash = to_ui2_struct(args)
+
+    begin
+      Chef::ReservedNames::Win32::Net.net_use_add_l2(nil, ui2_hash)
+    rescue Chef::Exceptions::Win32APIError => e
+      raise ArgumentError, e
     end
   end
 
-  def get_info
-    ptr  = 0.chr * PTR_SIZE
-    rc = NetUseGetInfo.call(nil, @name, 2, ptr)
-
-    if rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
+  def from_use_info_struct(ui2_hash)
+    ui2_hash.inject({}) do |memo, (k,v)|
+      memo[k.to_s.sub('ui2_', '').to_sym] = v
+      memo
     end
+  end
 
-    ptr = ptr.unpack('L')[0]
-    buffer = 0.chr * SIZEOF_USE_INFO_2
-    memcpy(buffer, ptr, buffer.size)
-    NetApiBufferFree(ptr)
-    use_info_2_unpack(buffer)
+  def get_info
+    begin
+      ui2 = Chef::ReservedNames::Win32::Net.net_use_get_info_l2(nil, use_name)
+      from_use_info_struct(ui2)
+    rescue Chef::Exceptions::Win32APIError => e
+      raise ArgumentError, e
+    end
   end
 
   def device
     get_info()[:remote]
   end
-  #XXX should we use some FORCE here?
+
   def delete
-    rc = NetUseDel.call(nil, @name, USE_NOFORCE)
-    if rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
+    begin
+      Chef::ReservedNames::Win32::Net.net_use_del(nil, use_name, :use_noforce)
+    rescue Chef::Exceptions::Win32APIError => e
+      raise ArgumentError, e
     end
   end
+
+  def use_name
+    @use_name
+  end
 end
diff --git a/lib/chef/util/windows/net_user.rb b/lib/chef/util/windows/net_user.rb
index 5df1a8a..4ce0512 100644
--- a/lib/chef/util/windows/net_user.rb
+++ b/lib/chef/util/windows/net_user.rb
@@ -18,98 +18,69 @@
 
 require 'chef/util/windows'
 require 'chef/exceptions'
+require 'chef/win32/net'
+require 'chef/win32/security'
 
 #wrapper around a subset of the NetUser* APIs.
 #nothing Chef specific, but not complete enough to be its own gem, so util for now.
 class Chef::Util::Windows::NetUser < Chef::Util::Windows
 
   private
-
-  LogonUser = Windows::API.new('LogonUser', 'SSSLLP', 'I', 'advapi32')
-
-  DOMAIN_GROUP_RID_USERS = 0x00000201
-
-  UF_SCRIPT              = 0x000001
-  UF_ACCOUNTDISABLE      = 0x000002
-  UF_PASSWD_CANT_CHANGE  = 0x000040
-  UF_NORMAL_ACCOUNT      = 0x000200
-  UF_DONT_EXPIRE_PASSWD  = 0x010000
-
-  #[:symbol_name, default_val]
-  #default_val duals as field type
-  #array index duals as structure offset
-
-  #OC-8391
-  #Changing [:password, nil], to [:password, ""],
-  #if :password is set to nil, windows user creation api ignores the password policy applied
-  #thus initializing it with empty string value.
-  USER_INFO_3 = [
-    [:name, nil],
-    [:password, ""],
-    [:password_age, 0],
-    [:priv, 0], #"The NetUserAdd and NetUserSetInfo functions ignore this member"
-    [:home_dir, nil],
-    [:comment, nil],
-    [:flags, UF_SCRIPT|UF_DONT_EXPIRE_PASSWD|UF_NORMAL_ACCOUNT],
-    [:script_path, nil],
-    [:auth_flags, 0],
-    [:full_name, nil],
-    [:user_comment, nil],
-    [:parms, nil],
-    [:workstations, nil],
-    [:last_logon, 0],
-    [:last_logoff, 0],
-    [:acct_expires, -1],
-    [:max_storage, -1],
-    [:units_per_week, 0],
-    [:logon_hours, nil],
-    [:bad_pw_count, 0],
-    [:num_logons, 0],
-    [:logon_server, nil],
-    [:country_code, 0],
-    [:code_page, 0],
-    [:user_id, 0],
-    [:primary_group_id, DOMAIN_GROUP_RID_USERS],
-    [:profile, nil],
-    [:home_dir_drive, nil],
-    [:password_expired, 0]
-  ]
-
-  USER_INFO_3_TEMPLATE =
-    USER_INFO_3.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join
-
-  SIZEOF_USER_INFO_3 = #sizeof(USER_INFO_3)
-    USER_INFO_3.inject(0){|sum,item|
-      sum + (item[1].class == Fixnum ? 4 : PTR_SIZE)
-    }
-
-  def user_info_3(args)
-    USER_INFO_3.collect { |field|
-      args.include?(field[0]) ? args[field[0]] : field[1]
-    }
-  end
-
-  def user_info_3_pack(user)
-    user.collect { |v|
-      v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v))
-    }.pack(USER_INFO_3_TEMPLATE)
+  NetUser = Chef::ReservedNames::Win32::NetUser
+  Security = Chef::ReservedNames::Win32::Security
+
+  USER_INFO_3_TRANSFORM = {
+    name: :usri3_name,
+    password: :usri3_password,
+    password_age: :usri3_password_age,
+    priv: :usri3_priv,
+    home_dir: :usri3_home_dir,
+    comment: :usri3_comment,
+    flags: :usri3_flags,
+    script_path: :usri3_script_path,
+    auth_flags: :usri3_auth_flags,
+    full_name: :usri3_full_name,
+    user_comment: :usri3_usr_comment,
+    parms: :usri3_parms,
+    workstations: :usri3_workstations,
+    last_logon: :usri3_last_logon,
+    last_logoff: :usri3_last_logoff,
+    acct_expires: :usri3_acct_expires,
+    max_storage: :usri3_max_storage,
+    units_per_week: :usri3_units_per_week,
+    logon_hours: :usri3_logon_hours,
+    bad_pw_count: :usri3_bad_pw_count,
+    num_logons: :usri3_num_logons,
+    logon_server: :usri3_logon_server,
+    country_code: :usri3_country_code,
+    code_page: :usri3_code_page,
+    user_id: :usri3_user_id,
+    primary_group_id: :usri3_primary_group_id,
+    profile: :usri3_profile,
+    home_dir_drive: :usri3_home_dir_drive,
+    password_expired: :usri3_password_expired,
+  }
+
+  def transform_usri3(args)
+    args.inject({}) do |memo, (k,v)|
+      memo[USER_INFO_3_TRANSFORM[k]] = v
+      memo
+    end
   end
 
-  def user_info_3_unpack(buffer)
-    user = Hash.new
-    USER_INFO_3.each_with_index do |field,offset|
-      user[field[0]] = field[1].class == Fixnum ?
-        dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset)
+  def usri3_to_hash(usri3)
+    t = USER_INFO_3_TRANSFORM.invert
+    usri3.inject({}) do |memo, (k,v)|
+      memo[t[k]] = v
+      memo
     end
-    user
   end
 
   def set_info(args)
-    user = user_info_3(args)
-    buffer = user_info_3_pack(user)
-    rc = NetUserSetInfo.call(nil, @name, 3, buffer, nil)
-    if rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
+    begin
+      rc = NetUser::net_user_set_info_l3(nil, @username, transform_usri3(args))
+    rescue Chef::Exceptions::Win32APIError => e
+      raise ArgumentError, e
     end
   end
 
@@ -117,52 +88,34 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
 
   def initialize(username)
     @username = username
-    @name = multi_to_wide(username)
   end
 
-  LOGON32_PROVIDER_DEFAULT = 0
-  LOGON32_LOGON_NETWORK = 3
+  LOGON32_PROVIDER_DEFAULT = Security::LOGON32_PROVIDER_DEFAULT
+  LOGON32_LOGON_NETWORK = Security::LOGON32_LOGON_NETWORK
   #XXX for an extra painful alternative, see: http://support.microsoft.com/kb/180548
   def validate_credentials(passwd)
-    token = 0.chr * PTR_SIZE
-    res = LogonUser.call(@username, nil, passwd,
-                         LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, token)
-    if res == 0
+    begin
+      token = Security::logon_user(@username, nil, passwd,
+                 LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT)
+      return true
+    rescue Chef::Exceptions::Win32APIError
       return false
     end
-    ::Windows::Handle::CloseHandle.call(token.unpack('L')[0])
-    return true
   end
 
   def get_info
-    ptr  = 0.chr * PTR_SIZE
-    rc = NetUserGetInfo.call(nil, @name, 3, ptr)
-
-    if rc == NERR_UserNotFound
-      raise Chef::Exceptions::UserIDNotFound, get_last_error(rc)
-    elsif rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
+    begin
+      ui3 = NetUser::net_user_get_info_l3(nil, @username)
+    rescue Chef::Exceptions::Win32APIError => e
+      raise ArgumentError, e
     end
-
-    ptr = ptr.unpack('L')[0]
-    buffer = 0.chr * SIZEOF_USER_INFO_3
-    memcpy(buffer, ptr, buffer.size)
-    NetApiBufferFree(ptr)
-    user_info_3_unpack(buffer)
+    usri3_to_hash(ui3)
   end
 
   def add(args)
-    user = user_info_3(args)
-    buffer = user_info_3_pack(user)
-
-    rc = NetUserAdd.call(nil, 3, buffer, rc)
-    if rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
-    end
-
-    #usri3_primary_group_id:
-    #"When you call the NetUserAdd function, this member must be DOMAIN_GROUP_RID_USERS"
-    NetLocalGroupAddMembers(nil, multi_to_wide("Users"), 3, buffer[0,PTR_SIZE], 1)
+    transformed_args = transform_usri3(args)
+    NetUser::net_user_add_l3(nil, transformed_args)
+    NetUser::net_local_group_add_member(nil, "Users", args[:name])
   end
 
   def user_modify(&proc)
@@ -182,15 +135,16 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
   end
 
   def delete
-    rc = NetUserDel.call(nil, @name)
-    if rc != NERR_Success
-      raise ArgumentError, get_last_error(rc)
+    begin
+      NetUser::net_user_del(nil, @username)
+    rescue Chef::Exceptions::Win32APIError => e
+      raise ArgumentError, e
     end
   end
 
   def disable_account
     user_modify do |user|
-      user[:flags] |= UF_ACCOUNTDISABLE
+      user[:flags] |= NetUser::UF_ACCOUNTDISABLE
       #This does not set the password to nil. It (for some reason) means to ignore updating the field.
       #See similar behavior for the logon_hours field documented at
       #http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
@@ -200,7 +154,7 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
 
   def enable_account
     user_modify do |user|
-      user[:flags] &= ~UF_ACCOUNTDISABLE
+      user[:flags] &= ~NetUser::UF_ACCOUNTDISABLE
       #This does not set the password to nil. It (for some reason) means to ignore updating the field.
       #See similar behavior for the logon_hours field documented at
       #http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
@@ -209,6 +163,6 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
   end
 
   def check_enabled
-    (get_info()[:flags] & UF_ACCOUNTDISABLE) != 0
+    (get_info()[:flags] & NetUser::UF_ACCOUNTDISABLE) != 0
   end
 end
diff --git a/lib/chef/util/windows/volume.rb b/lib/chef/util/windows/volume.rb
index 08c3a53..6e45594 100644
--- a/lib/chef/util/windows/volume.rb
+++ b/lib/chef/util/windows/volume.rb
@@ -18,42 +18,42 @@
 
 #simple wrapper around Volume APIs. might be possible with WMI, but possibly more complex.
 
+require 'chef/win32/api/file'
 require 'chef/util/windows'
-require 'windows/volume'
 
 class Chef::Util::Windows::Volume < Chef::Util::Windows
-
-  private
-  include Windows::Volume
-  #XXX not defined in the current windows-pr release
-  DeleteVolumeMountPoint =
-    Windows::API.new('DeleteVolumeMountPoint', 'S', 'B') unless defined? DeleteVolumeMountPoint
-
-  public
+  attr_reader :mount_point
 
   def initialize(name)
     name += "\\" unless name =~ /\\$/ #trailing slash required
-    @name = name
+    @mount_point = name
   end
 
   def device
-    buffer = 0.chr * 256
-    if GetVolumeNameForVolumeMountPoint(@name, buffer, buffer.size)
-      return buffer[0,buffer.size].unpack("Z*")[0]
-    else
-      raise ArgumentError, get_last_error
+    begin
+      Chef::ReservedNames::Win32::File.get_volume_name_for_volume_mount_point(mount_point)
+    rescue Chef::Exceptions::Win32APIError => e
+      raise ArgumentError, e
     end
   end
 
   def delete
-    unless DeleteVolumeMountPoint.call(@name)
-      raise ArgumentError, get_last_error
+    begin
+      Chef::ReservedNames::Win32::File.delete_volume_mount_point(mount_point)
+    rescue Chef::Exceptions::Win32APIError => e
+      raise ArgumentError, e
     end
   end
 
   def add(args)
-    unless SetVolumeMountPoint(@name, args[:remote])
-      raise ArgumentError, get_last_error
+    begin
+      Chef::ReservedNames::Win32::File.set_volume_mount_point(mount_point, args[:remote])
+    rescue Chef::Exceptions::Win32APIError => e
+      raise ArgumentError, e
     end
   end
+
+  def mount_point
+    @mount_point
+  end
 end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index c2f287c..3eb7d49 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -1,6 +1,4 @@
-
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2010-2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,9 +13,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# NOTE: This file is generated by running `rake version` in the top level of
+# this repo. Do not edit this manually. Edit the VERSION file and run the rake
+# task instead.
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
 class Chef
   CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
-  VERSION = '12.3.0'
+  VERSION = '12.6.0'
 end
 
 #
@@ -25,6 +29,6 @@ end
 #
 # NOTE: DO NOT Use the Chef::Version class on Chef::VERSIONs.  The
 #       Chef::Version class is for _cookbooks_ only, and cannot handle
-#       pre-release chef-client versions like "10.14.0.rc.2".  Please
-#       use Rubygem's Gem::Version class instead.
+#       pre-release versions like "10.14.0.rc.2".  Please use Rubygem's
+#       Gem::Version class instead.
 #
diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb
index efa632f..4786222 100644
--- a/lib/chef/win32/api.rb
+++ b/lib/chef/win32/api.rb
@@ -67,7 +67,7 @@ class Chef
         # BaseTsd.h: #ifdef (_WIN64) host.typedef int HALF_PTR; #else host.typedef short HALF_PTR;
         host.typedef :ulong,   :HACCEL # (L) Handle to an accelerator table. WinDef.h: #host.typedef HANDLE HACCEL;
         # See http://msdn.microsoft.com/en-us/library/ms645526%28VS.85%29.aspx
-        host.typedef :ulong,   :HANDLE # (L) Handle to an object. WinNT.h: #host.typedef PVOID HANDLE;
+        host.typedef :size_t,   :HANDLE # (L) Handle to an object. WinNT.h: #host.typedef PVOID HANDLE;
         # todo: Platform-dependent! Need to change to :uint64 for Win64
         host.typedef :ulong,   :HBITMAP # (L) Handle to a bitmap: http://msdn.microsoft.com/en-us/library/dd183377%28VS.85%29.aspx
         host.typedef :ulong,   :HBRUSH # (L) Handle to a brush. http://msdn.microsoft.com/en-us/library/dd183394%28VS.85%29.aspx
@@ -117,6 +117,7 @@ class Chef
         host.typedef :uint32,  :LCID # Locale identifier. For more information, see Locales.
         host.typedef :uint32,  :LCTYPE # Locale information type. For a list, see Locale Information Constants.
         host.typedef :uint32,  :LGRPID # Language group identifier. For a list, see EnumLanguageGroupLocales.
+        host.typedef :pointer, :LMSTR # Pointer to null termiated string of unicode characters
         host.typedef :long,    :LONG # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal.
         host.typedef :int32,   :LONG32 # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal.
         host.typedef :int64,   :LONG64 # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807
@@ -187,6 +188,7 @@ class Chef
         host.typedef :pointer, :PCRYPTPROTECT_PROMPTSTRUCT # Pointer to a CRYPTOPROTECT_PROMPTSTRUCT.
         host.typedef :pointer, :PDATA_BLOB # Pointer to a DATA_BLOB.
         host.typedef :pointer, :PTSTR # A PWSTR if UNICODE is defined, a PSTR otherwise.
+        host.typedef :pointer, :PSID
         host.typedef :pointer, :PUCHAR # Pointer to a UCHAR.
         host.typedef :pointer, :PUHALF_PTR # Pointer to a UHALF_PTR.
         host.typedef :pointer, :PUINT # Pointer to a UINT.
diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb
index 86b2b94..3618d12 100644
--- a/lib/chef/win32/api/file.rb
+++ b/lib/chef/win32/api/file.rb
@@ -20,6 +20,7 @@
 require 'chef/win32/api'
 require 'chef/win32/api/security'
 require 'chef/win32/api/system'
+require 'chef/win32/unicode'
 
 class Chef
   module ReservedNames::Win32
@@ -181,7 +182,14 @@ class Chef
         # Win32 API Bindings
         ###############################################
 
-        ffi_lib 'kernel32'
+        ffi_lib 'kernel32', 'version'
+
+        # Does not map directly to a win32 struct
+        # see https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx
+        class Translation < FFI::Struct
+          layout :w_lang, :WORD,
+          :w_code_page, :WORD
+        end
 
 =begin
 typedef struct _FILETIME {
@@ -450,6 +458,53 @@ BOOL WINAPI DeviceIoControl(
 =end
         safe_attach_function :DeviceIoControl, [:HANDLE, :DWORD, :LPVOID, :DWORD, :LPVOID, :DWORD, :LPDWORD, :pointer], :BOOL
 
+
+#BOOL WINAPI DeleteVolumeMountPoint(
+  #_In_ LPCTSTR lpszVolumeMountPoint
+#);
+        safe_attach_function :DeleteVolumeMountPointW, [:LPCTSTR], :BOOL
+
+#BOOL WINAPI SetVolumeMountPoint(
+  #_In_ LPCTSTR lpszVolumeMountPoint,
+  #_In_ LPCTSTR lpszVolumeName
+#);
+        safe_attach_function :SetVolumeMountPointW, [:LPCTSTR, :LPCTSTR], :BOOL
+
+#BOOL WINAPI GetVolumeNameForVolumeMountPoint(
+  #_In_  LPCTSTR lpszVolumeMountPoint,
+  #_Out_ LPTSTR  lpszVolumeName,
+  #_In_  DWORD   cchBufferLength
+#);
+        safe_attach_function :GetVolumeNameForVolumeMountPointW, [:LPCTSTR, :LPTSTR, :DWORD], :BOOL
+
+=begin
+BOOL WINAPI GetFileVersionInfo(
+  _In_       LPCTSTR lptstrFilename,
+  _Reserved_ DWORD   dwHandle,
+  _In_       DWORD   dwLen,
+  _Out_      LPVOID  lpData
+);
+=end
+        safe_attach_function :GetFileVersionInfoW, [:LPCTSTR, :DWORD, :DWORD, :LPVOID], :BOOL
+
+=begin
+DWORD WINAPI GetFileVersionInfoSize(
+  _In_      LPCTSTR lptstrFilename,
+  _Out_opt_ LPDWORD lpdwHandle
+);
+=end
+        safe_attach_function :GetFileVersionInfoSizeW, [:LPCTSTR, :LPDWORD], :DWORD
+
+=begin
+BOOL WINAPI VerQueryValue(
+  _In_  LPCVOID pBlock,
+  _In_  LPCTSTR lpSubBlock,
+  _Out_ LPVOID  *lplpBuffer,
+  _Out_ PUINT   puLen
+);
+=end
+        safe_attach_function :VerQueryValueW, [:LPCVOID, :LPCTSTR, :LPVOID, :PUINT], :BOOL
+
         ###############################################
         # Helpers
         ###############################################
@@ -545,6 +600,21 @@ BOOL WINAPI DeviceIoControl(
           file_information
         end
 
+        def retrieve_file_version_info(file_name)
+          file_name = encode_path(file_name)
+          file_size = GetFileVersionInfoSizeW(file_name, nil)
+          if file_size == 0
+            Chef::ReservedNames::Win32::Error.raise!
+          end
+          
+          version_info = FFI::MemoryPointer.new(file_size)
+          unless GetFileVersionInfoW(file_name, 0, file_size, version_info)
+            Chef::ReservedNames::Win32::Error.raise!
+          end
+
+          version_info
+        end
+
       end
     end
   end
diff --git a/lib/chef/win32/api/installer.rb b/lib/chef/win32/api/installer.rb
index 6864a26..b4851ec 100644
--- a/lib/chef/win32/api/installer.rb
+++ b/lib/chef/win32/api/installer.rb
@@ -158,7 +158,7 @@ UINT MsiCloseHandle(
             raise Chef::Exceptions::Package, msg
           end
 
-          version
+          version.chomp(0.chr)
         end
       end
     end
diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb
index eeb2b07..b173987 100644
--- a/lib/chef/win32/api/net.rb
+++ b/lib/chef/win32/api/net.rb
@@ -17,6 +17,7 @@
 #
 
 require 'chef/win32/api'
+require 'chef/win32/unicode'
 
 class Chef
   module ReservedNames::Win32
@@ -32,12 +33,76 @@ class Chef
 
         MAX_PREFERRED_LENGTH                = 0xFFFF
 
-        NERR_Success                        = 0
-        NERR_UserNotFound                   = 2221
+        DOMAIN_GROUP_RID_USERS = 0x00000201
+
+        UF_SCRIPT              = 0x000001
+        UF_ACCOUNTDISABLE      = 0x000002
+        UF_PASSWD_CANT_CHANGE  = 0x000040
+        UF_NORMAL_ACCOUNT      = 0x000200
+        UF_DONT_EXPIRE_PASSWD  = 0x010000
+
+        USE_NOFORCE = 0
+        USE_FORCE = 1
+        USE_LOTS_OF_FORCE = 2 #every windows API should support this flag
+
+        NERR_Success = 0
+        NERR_InvalidComputer = 2351
+        NERR_NotPrimary = 2226
+        NERR_SpeGroupOp = 2234
+        NERR_LastAdmin = 2452
+        NERR_BadUsername = 2202
+        NERR_BadPassword = 2203
+        NERR_PasswordTooShort = 2245
+        NERR_UserNotFound = 2221
+        NERR_GroupNotFound = 2220
+        ERROR_ACCESS_DENIED = 5
+        ERROR_MORE_DATA = 234
 
         ffi_lib "netapi32"
 
+        module StructHelpers
+          def set(key, val)
+            val = if val.is_a? String
+              encoded = if val.encoding == Encoding::UTF_16LE
+                val
+              else
+                val.to_wstring
+              end
+              FFI::MemoryPointer.from_string(encoded)
+            else
+              val
+            end
+            self[key] = val
+          end
+
+          def get(key)
+            if respond_to? key
+             send(key)
+            else
+              val = self[key]
+              if val.is_a? FFI::Pointer
+                if val.null?
+                  nil
+                else
+                  val.read_wstring
+                end
+              else
+                val
+              end
+            end
+          end
+
+          def as_ruby
+            members.inject({}) do |memo, key|
+              memo[key] = get(key)
+              memo
+            end
+          end
+        end
+
+
         class USER_INFO_3 < FFI::Struct
+          include StructHelpers
           layout :usri3_name, :LPWSTR,
             :usri3_password, :LPWSTR,
             :usri3_password_age, :DWORD,
@@ -67,8 +132,75 @@ class Chef
             :usri3_profile, :LPWSTR,
             :usri3_home_dir_drive, :LPWSTR,
             :usri3_password_expired, :DWORD
+
+          def usri3_logon_hours
+            val = self[:usri3_logon_hours]
+            if !val.nil? && !val.null?
+              val.read_bytes(21)
+            else
+              nil
+            end
+          end
         end
 
+        class LOCALGROUP_MEMBERS_INFO_0 < FFI::Struct
+          layout :lgrmi0_sid, :PSID
+        end
+
+        class LOCALGROUP_MEMBERS_INFO_3 < FFI::Struct
+          layout :lgrmi3_domainandname, :LPWSTR
+        end
+
+        class LOCALGROUP_INFO_0 < FFI::Struct
+          layout :lgrpi0_name, :LPWSTR
+        end
+
+        class USE_INFO_2 < FFI::Struct
+          include StructHelpers
+
+          layout :ui2_local, :LMSTR,
+            :ui2_remote, :LMSTR,
+            :ui2_password, :LMSTR,
+            :ui2_status, :DWORD,
+            :ui2_asg_type, :DWORD,
+            :ui2_refcount, :DWORD,
+            :ui2_usecount, :DWORD,
+            :ui2_username, :LPWSTR,
+            :ui2_domainname, :LMSTR
+        end
+
+
+#NET_API_STATUS NetLocalGroupAdd(
+  #_In_  LPCWSTR servername,
+  #_In_  DWORD   level,
+  #_In_  LPBYTE  buf,
+  #_Out_ LPDWORD parm_err
+#);
+        safe_attach_function :NetLocalGroupAdd, [
+          :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD
+        ], :DWORD
+
+#NET_API_STATUS NetLocalGroupDel(
+  #_In_ LPCWSTR servername,
+  #_In_ LPCWSTR groupname
+#);
+        safe_attach_function :NetLocalGroupDel, [:LPCWSTR, :LPCWSTR], :DWORD
+
+#NET_API_STATUS NetLocalGroupGetMembers(
+  #_In_    LPCWSTR    servername,
+  #_In_    LPCWSTR    localgroupname,
+  #_In_    DWORD      level,
+  #_Out_   LPBYTE     *bufptr,
+  #_In_    DWORD      prefmaxlen,
+  #_Out_   LPDWORD    entriesread,
+  #_Out_   LPDWORD    totalentries,
+  #_Inout_ PDWORD_PTR resumehandle
+#);
+        safe_attach_function :NetLocalGroupGetMembers, [
+          :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD,
+          :LPDWORD, :LPDWORD, :PDWORD_PTR
+        ], :DWORD
+
 # NET_API_STATUS NetUserEnum(
 #   _In_     LPCWSTR servername,
 #   _In_     DWORD level,
@@ -79,12 +211,113 @@ class Chef
 #   _Out_    LPDWORD totalentries,
 #   _Inout_  LPDWORD resume_handle
 # );
-        safe_attach_function :NetUserEnum, [ :LPCWSTR, :DWORD, :DWORD, :LPBYTE, :DWORD, :LPDWORD, :LPDWORD, :LPDWORD ], :DWORD
+        safe_attach_function :NetUserEnum, [
+          :LPCWSTR, :DWORD, :DWORD, :LPBYTE,
+          :DWORD, :LPDWORD, :LPDWORD, :LPDWORD
+        ], :DWORD
 
 # NET_API_STATUS NetApiBufferFree(
 #   _In_  LPVOID Buffer
 # );
-        safe_attach_function :NetApiBufferFree, [ :LPVOID ], :DWORD
+        safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD
+
+#NET_API_STATUS NetUserAdd(
+  #_In_  LMSTR   servername,
+  #_In_  DWORD   level,
+  #_In_  LPBYTE  buf,
+  #_Out_ LPDWORD parm_err
+#);
+        safe_attach_function :NetUserAdd, [
+          :LMSTR, :DWORD, :LPBYTE, :LPDWORD
+        ], :DWORD
+
+#NET_API_STATUS NetLocalGroupAddMembers(
+#  _In_ LPCWSTR servername,
+#  _In_ LPCWSTR groupname,
+#  _In_ DWORD   level,
+#  _In_ LPBYTE  buf,
+#  _In_ DWORD   totalentries
+#);
+        safe_attach_function :NetLocalGroupAddMembers, [
+          :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+        ], :DWORD
+
+#NET_API_STATUS NetLocalGroupSetMembers(
+#  _In_ LPCWSTR servername,
+#  _In_ LPCWSTR groupname,
+#  _In_ DWORD   level,
+#  _In_ LPBYTE  buf,
+#  _In_ DWORD   totalentries
+#);
+        safe_attach_function :NetLocalGroupSetMembers, [
+          :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+        ], :DWORD
+
+#NET_API_STATUS NetLocalGroupDelMembers(
+#  _In_ LPCWSTR servername,
+#  _In_ LPCWSTR groupname,
+#  _In_ DWORD   level,
+#  _In_ LPBYTE  buf,
+#  _In_ DWORD   totalentries
+#);
+        safe_attach_function :NetLocalGroupDelMembers, [
+          :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+        ], :DWORD
+
+#NET_API_STATUS NetUserGetInfo(
+#  _In_  LPCWSTR servername,
+#  _In_  LPCWSTR username,
+#  _In_  DWORD   level,
+#  _Out_ LPBYTE  *bufptr
+#);
+        safe_attach_function :NetUserGetInfo, [
+          :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE
+        ], :DWORD
+
+#NET_API_STATUS NetApiBufferFree(
+#  _In_ LPVOID Buffer
+#);
+        safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD
+
+#NET_API_STATUS NetUserSetInfo(
+#  _In_  LPCWSTR servername,
+#  _In_  LPCWSTR username,
+#  _In_  DWORD   level,
+#  _In_  LPBYTE  buf,
+#  _Out_ LPDWORD parm_err
+#);
+        safe_attach_function :NetUserSetInfo, [
+          :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD
+        ], :DWORD
+
+#NET_API_STATUS NetUserDel(
+#  _In_ LPCWSTR servername,
+#  _In_ LPCWSTR username
+#);
+        safe_attach_function :NetUserDel, [:LPCWSTR, :LPCWSTR], :DWORD
+
+#NET_API_STATUS NetUseDel(
+  #_In_ LMSTR UncServerName,
+  #_In_ LMSTR UseName,
+  #_In_ DWORD ForceCond
+#);
+        safe_attach_function :NetUseDel, [:LMSTR, :LMSTR, :DWORD], :DWORD
+
+#NET_API_STATUS NetUseGetInfo(
+  #_In_  LMSTR  UncServerName,
+  #_In_  LMSTR  UseName,
+  #_In_  DWORD  Level,
+  #_Out_ LPBYTE *BufPtr
+#);
+        safe_attach_function :NetUseGetInfo, [:LMSTR, :LMSTR, :DWORD, :pointer], :DWORD
+
+#NET_API_STATUS NetUseAdd(
+  #_In_  LMSTR   UncServerName,
+  #_In_  DWORD   Level,
+  #_In_  LPBYTE  Buf,
+  #_Out_ LPDWORD ParmError
+#);
+        safe_attach_function :NetUseAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD
       end
     end
   end
diff --git a/lib/chef/win32/api/registry.rb b/lib/chef/win32/api/registry.rb
new file mode 100644
index 0000000..cbbf6b6
--- /dev/null
+++ b/lib/chef/win32/api/registry.rb
@@ -0,0 +1,51 @@
+#
+# Author:: Salim Alam (<salam at chef.io>)
+# Copyright:: Copyright 2015 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/win32/api'
+
+class Chef
+  module ReservedNames::Win32
+    module API
+      module Registry
+        extend Chef::ReservedNames::Win32::API
+
+        ###############################################
+        # Win32 API Bindings
+        ###############################################
+
+        ffi_lib 'advapi32'
+
+        # LONG WINAPI RegDeleteKeyEx(
+        #   _In_       HKEY    hKey,
+        #   _In_       LPCTSTR lpSubKey,
+        #   _In_       REGSAM  samDesired,
+        #   _Reserved_ DWORD   Reserved
+        # );
+        safe_attach_function :RegDeleteKeyExW, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG
+        safe_attach_function :RegDeleteKeyExA, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG
+
+        # LONG WINAPI RegDeleteValue(
+        #   _In_     HKEY    hKey,
+        #   _In_opt_ LPCTSTR lpValueName
+        # );
+        safe_attach_function :RegDeleteValueW, [ :HKEY, :LPCTSTR ], :LONG
+
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/chef/win32/api/security.rb b/lib/chef/win32/api/security.rb
index 229f2ac..4c352a3 100644
--- a/lib/chef/win32/api/security.rb
+++ b/lib/chef/win32/api/security.rb
@@ -193,6 +193,20 @@ class Chef
 
         MAXDWORD = 0xffffffff
 
+        # LOGON32 constants for LogonUser
+        LOGON32_LOGON_INTERACTIVE = 2;
+        LOGON32_LOGON_NETWORK = 3;
+        LOGON32_LOGON_BATCH = 4;
+        LOGON32_LOGON_SERVICE = 5;
+        LOGON32_LOGON_UNLOCK = 7;
+        LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
+        LOGON32_LOGON_NEW_CREDENTIALS = 9;
+
+        LOGON32_PROVIDER_DEFAULT = 0;
+        LOGON32_PROVIDER_WINNT35 = 1;
+        LOGON32_PROVIDER_WINNT40 = 2;
+        LOGON32_PROVIDER_WINNT50 = 3;
+
         ###############################################
         # Win32 API Bindings
         ###############################################
@@ -270,6 +284,14 @@ class Chef
              :MaxTokenInfoClass
         ]
 
+        class TOKEN_OWNER < FFI::Struct
+          layout :Owner, :pointer
+        end
+
+        class TOKEN_PRIMARY_GROUP < FFI::Struct
+          layout :PrimaryGroup, :pointer
+        end
+
         # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572%28v=vs.85%29.aspx
         SECURITY_IMPERSONATION_LEVEL = enum :SECURITY_IMPERSONATION_LEVEL, [
              :SecurityAnonymous,
@@ -405,6 +427,8 @@ class Chef
         safe_attach_function :SetSecurityDescriptorOwner, [ :pointer, :pointer, :BOOL ], :BOOL
         safe_attach_function :SetSecurityDescriptorSacl, [ :pointer, :BOOL, :pointer, :BOOL ], :BOOL
         safe_attach_function :GetTokenInformation, [ :HANDLE, :TOKEN_INFORMATION_CLASS, :pointer, :DWORD, :PDWORD ], :BOOL
+        safe_attach_function :LogonUserW, [:LPTSTR, :LPTSTR, :LPTSTR, :DWORD, :DWORD, :PHANDLE], :BOOL
+
       end
     end
   end
diff --git a/lib/chef/win32/api/system.rb b/lib/chef/win32/api/system.rb
index d57699a..a485f89 100644
--- a/lib/chef/win32/api/system.rb
+++ b/lib/chef/win32/api/system.rb
@@ -187,6 +187,29 @@ int WINAPI GetSystemMetrics(
         safe_attach_function :GetSystemMetrics, [:int], :int
 
 =begin
+UINT WINAPI GetSystemWow64Directory(
+  _Out_ LPTSTR lpBuffer,
+  _In_  UINT   uSize
+);
+=end
+        safe_attach_function :GetSystemWow64DirectoryW, [:LPTSTR, :UINT], :UINT
+        safe_attach_function :GetSystemWow64DirectoryA, [:LPTSTR, :UINT], :UINT
+
+=begin
+BOOL WINAPI Wow64DisableWow64FsRedirection(
+  _Out_ PVOID *OldValue
+);
+=end
+        safe_attach_function :Wow64DisableWow64FsRedirection, [:PVOID], :BOOL
+
+=begin
+BOOL WINAPI Wow64RevertWow64FsRedirection(
+  _In_ PVOID OldValue
+);
+=end
+        safe_attach_function :Wow64RevertWow64FsRedirection, [:PVOID], :BOOL
+
+=begin
 LRESULT WINAPI SendMessageTimeout(
   _In_       HWND hWnd,
   _In_       UINT Msg,
diff --git a/lib/chef/win32/api/unicode.rb b/lib/chef/win32/api/unicode.rb
index 0b2cb09..2a9166a 100644
--- a/lib/chef/win32/api/unicode.rb
+++ b/lib/chef/win32/api/unicode.rb
@@ -129,49 +129,6 @@ int WideCharToMultiByte(
 =end
         safe_attach_function :WideCharToMultiByte, [:UINT, :DWORD, :LPCWSTR, :int, :LPSTR, :int, :LPCSTR, :LPBOOL], :int
 
-        ###############################################
-        # Helpers
-        ###############################################
-
-        def utf8_to_wide(ustring)
-          # ensure it is actually UTF-8
-          # Ruby likes to mark binary data as ASCII-8BIT
-          ustring = (ustring + "").force_encoding('UTF-8') if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8"
-
-          # ensure we have the double-null termination Windows Wide likes
-          ustring = ustring + "\000\000" if ustring[-1].chr != "\000"
-
-          # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
-          ustring = begin
-            if ustring.respond_to?(:encode)
-              ustring.encode('UTF-16LE')
-            else
-              require 'iconv'
-              Iconv.conv("UTF-16LE", "UTF-8", ustring)
-            end
-          end
-          ustring
-        end
-
-        def wide_to_utf8(wstring)
-          # ensure it is actually UTF-16LE
-          # Ruby likes to mark binary data as ASCII-8BIT
-          wstring = wstring.force_encoding('UTF-16LE') if wstring.respond_to?(:force_encoding)
-
-          # encode it all as UTF-8
-          wstring = begin
-            if wstring.respond_to?(:encode)
-              wstring.encode('UTF-8')
-            else
-              require 'iconv'
-              Iconv.conv("UTF-8", "UTF-16LE", wstring)
-            end
-          end
-          # remove trailing CRLF and NULL characters
-          wstring.strip!
-          wstring
-        end
-
       end
     end
   end
diff --git a/lib/chef/win32/crypto.rb b/lib/chef/win32/crypto.rb
index 79cf51b..e9c1da9 100644
--- a/lib/chef/win32/crypto.rb
+++ b/lib/chef/win32/crypto.rb
@@ -19,6 +19,7 @@
 require 'chef/win32/error'
 require 'chef/win32/api/memory'
 require 'chef/win32/api/crypto'
+require 'chef/win32/unicode'
 require 'digest'
 
 class Chef
@@ -29,7 +30,7 @@ class Chef
 
       def self.encrypt(str, &block)
         data_blob = CRYPT_INTEGER_BLOB.new
-        unless CryptProtectData(CRYPT_INTEGER_BLOB.new(str.to_wstring), nil, nil, nil, nil, 0, data_blob)
+        unless CryptProtectData(CRYPT_INTEGER_BLOB.new(str.to_wstring), nil, nil, nil, nil, CRYPTPROTECT_LOCAL_MACHINE, data_blob)
           Chef::ReservedNames::Win32::Error.raise!
         end
         bytes = data_blob[:pbData].get_bytes(0, data_blob[:cbData])
diff --git a/lib/chef/util/powershell/ps_credential.rb b/lib/chef/win32/eventlog.rb
similarity index 54%
copy from lib/chef/util/powershell/ps_credential.rb
copy to lib/chef/win32/eventlog.rb
index 01f8c27..24af2da 100644
--- a/lib/chef/util/powershell/ps_credential.rb
+++ b/lib/chef/win32/eventlog.rb
@@ -1,7 +1,7 @@
 #
 # Author:: Jay Mundrawala (<jdm at chef.io>)
 #
-# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# Copyright:: 2015, 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.
@@ -16,23 +16,16 @@
 # limitations under the License.
 #
 
-require 'chef/win32/crypto' if Chef::Platform.windows?
-
-class Chef::Util::Powershell
-  class PSCredential
-    def initialize(username, password)
-      @username = username
-      @password = password
-    end
-
-    def to_psobject
-      "New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
+if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
+  if !defined? Chef::Win32EventLogLoaded
+    if defined? Windows::Constants
+      [:INFINITE, :WAIT_FAILED, :FORMAT_MESSAGE_IGNORE_INSERTS, :ERROR_INSUFFICIENT_BUFFER].each do |c|
+        # These are redefined in 'win32/eventlog'
+        Windows::Constants.send(:remove_const, c) if Windows::Constants.const_defined? c
+      end
     end
 
-    private
-
-    def encrypt(str)
-      Chef::ReservedNames::Win32::Crypto.encrypt(str)
-    end
+    require 'win32/eventlog'
+    Chef::Win32EventLogLoaded = true
   end
 end
diff --git a/lib/chef/win32/file.rb b/lib/chef/win32/file.rb
index e6640ca..abfad91 100644
--- a/lib/chef/win32/file.rb
+++ b/lib/chef/win32/file.rb
@@ -17,9 +17,11 @@
 # limitations under the License.
 #
 
+require 'chef/mixin/wide_string'
 require 'chef/win32/api/file'
 require 'chef/win32/api/security'
 require 'chef/win32/error'
+require 'chef/win32/unicode'
 
 class Chef
   module ReservedNames::Win32
@@ -27,6 +29,9 @@ class Chef
       include Chef::ReservedNames::Win32::API::File
       extend Chef::ReservedNames::Win32::API::File
 
+      include Chef::Mixin::WideString
+      extend Chef::Mixin::WideString
+
       # Creates a symbolic link called +new_name+ for the file or directory
       # +old_name+.
       #
@@ -145,6 +150,10 @@ class Chef
         Info.new(file_name)
       end
 
+      def self.version_info(file_name)
+        VersionInfo.new(file_name)
+      end
+
       def self.verify_links_supported!
         begin
           CreateSymbolicLinkW(nil)
@@ -157,9 +166,9 @@ class Chef
 
       def self.file_access_check(path, desired_access)
         security_descriptor = Chef::ReservedNames::Win32::Security.get_file_security(path)
-        token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE | 
+        token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE |
                        Chef::ReservedNames::Win32::Security::TOKEN_QUERY |
-                       Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE | 
+                       Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE |
                        Chef::ReservedNames::Win32::Security::STANDARD_RIGHTS_READ
         token = Chef::ReservedNames::Win32::Security.open_process_token(
           Chef::ReservedNames::Win32::Process.get_current_process,
@@ -172,10 +181,30 @@ class Chef
         mapping[:GenericExecute] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_EXECUTE
         mapping[:GenericAll] = Chef::ReservedNames::Win32::Security::FILE_ALL_ACCESS
 
-        Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token, 
+        Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token,
                                                           desired_access, mapping)
       end
 
+      def self.delete_volume_mount_point(mount_point)
+        unless DeleteVolumeMountPointW(wstring(mount_point))
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+      end
+
+      def self.set_volume_mount_point(mount_point, name)
+        unless SetVolumeMountPointW(wstring(mount_point), wstring(name))
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+      end
+
+      def self.get_volume_name_for_volume_mount_point(mount_point)
+        buffer = FFI::MemoryPointer.new(2, 128)
+        unless GetVolumeNameForVolumeMountPointW(wstring(mount_point), buffer, buffer.size/buffer.type_size)
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+        buffer.read_wstring
+      end
+
       # ::File compat
       class << self
         alias :stat :info
@@ -186,3 +215,4 @@ class Chef
 end
 
 require 'chef/win32/file/info'
+require 'chef/win32/file/version_info'
diff --git a/lib/chef/win32/file/version_info.rb b/lib/chef/win32/file/version_info.rb
new file mode 100644
index 0000000..2974c8a
--- /dev/null
+++ b/lib/chef/win32/file/version_info.rb
@@ -0,0 +1,93 @@
+#
+# Author:: Matt Wrock (<matt at mattwrock.com>)
+# Copyright:: Copyright 2015 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/win32/file'
+
+class Chef
+  module ReservedNames::Win32
+    class File
+
+      class VersionInfo
+
+        include Chef::ReservedNames::Win32::API::File
+
+        def initialize(file_name)
+          raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
+          @file_version_info = retrieve_file_version_info(file_name)
+        end
+
+        # defining method for each predefined version resource string
+        # see https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx
+        [
+          :Comments,
+          :CompanyName,
+          :FileDescription,
+          :FileVersion,
+          :InternalName,
+          :LegalCopyright,
+          :LegalTrademarks,
+          :OriginalFilename,
+          :ProductName,
+          :ProductVersion,
+          :PrivateBuild,
+          :SpecialBuild
+        ].each do |method|
+          define_method method do
+            begin
+              get_version_info_string(method.to_s)
+            rescue Chef::Exceptions::Win32APIError
+              return nil
+            end
+          end
+        end
+
+        private
+
+        def translation
+          @translation ||= begin
+            info_ptr = FFI::MemoryPointer.new(:pointer)
+            unless VerQueryValueW(@file_version_info, "\\VarFileInfo\\Translation".to_wstring, info_ptr, FFI::MemoryPointer.new(:int))
+              Chef::ReservedNames::Win32::Error.raise!
+            end
+
+            # there can potentially be multiple translations but most installers just have one
+            # we use the first because we use this for the version strings which are language
+            # agnostic. If/when we need other fields, we should we should add logic to find
+            # the "best" translation
+            trans = Translation.new(info_ptr.read_pointer)
+            to_hex(trans[:w_lang]) + to_hex(trans[:w_code_page])
+          end
+        end
+
+        def to_hex(integer)
+          integer.to_s(16).rjust(4,"0")
+        end
+
+        def get_version_info_string(string_key)
+          info_ptr = FFI::MemoryPointer.new(:pointer)
+          size_ptr = FFI::MemoryPointer.new(:int)
+          unless VerQueryValueW(@file_version_info, "\\StringFileInfo\\#{translation}\\#{string_key}".to_wstring, info_ptr, size_ptr)
+            Chef::ReservedNames::Win32::Error.raise!
+          end
+
+          info_ptr.read_pointer.read_wstring(size_ptr.read_uint)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/win32/mutex.rb b/lib/chef/win32/mutex.rb
index 0b7d99f..0d8eba1 100644
--- a/lib/chef/win32/mutex.rb
+++ b/lib/chef/win32/mutex.rb
@@ -17,6 +17,7 @@
 #
 
 require 'chef/win32/api/synchronization'
+require 'chef/win32/unicode'
 
 class Chef
   module ReservedNames::Win32
@@ -78,7 +79,7 @@ class Chef
           # of the process goes away and this class is only being used
           # to synchronize chef-clients runs on a node.
           Chef::Log.error("Can not release mutex '#{name}'. This might cause issues \
-if the mutex is attempted to be acquired by other threads.")
+if other threads attempt to acquire the mutex.")
           Chef::ReservedNames::Win32::Error.raise!
         end
       end
@@ -113,5 +114,3 @@ if the mutex is attempted to be acquired by other threads.")
     end
   end
 end
-
-
diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb
new file mode 100644
index 0000000..59f29c4
--- /dev/null
+++ b/lib/chef/win32/net.rb
@@ -0,0 +1,344 @@
+#
+# Author:: Jay Mundrawala(<jdm at chef.io>)
+# Copyright:: Copyright 2015 Chef Software
+# 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/win32/api/net'
+require 'chef/win32/error'
+require 'chef/mixin/wide_string'
+
+class Chef
+  module ReservedNames::Win32
+    class Net
+      include Chef::ReservedNames::Win32::API::Error
+      extend Chef::ReservedNames::Win32::API::Error
+
+      include Chef::ReservedNames::Win32::API::Net
+      extend Chef::ReservedNames::Win32::API::Net
+
+      include Chef::Mixin::WideString
+      extend Chef::Mixin::WideString
+
+      def self.default_user_info_3
+        ui3 = USER_INFO_3.new.tap do |s|
+          { usri3_name: nil,
+            usri3_password: nil,
+            usri3_password_age: 0,
+            usri3_priv: 0,
+            usri3_home_dir: nil,
+            usri3_comment: nil,
+            usri3_flags: UF_SCRIPT|UF_DONT_EXPIRE_PASSWD|UF_NORMAL_ACCOUNT,
+            usri3_script_path: nil,
+            usri3_auth_flags: 0,
+            usri3_full_name: nil,
+            usri3_usr_comment: nil,
+            usri3_parms: nil,
+            usri3_workstations: nil,
+            usri3_last_logon: 0,
+            usri3_last_logoff: 0,
+            usri3_acct_expires: -1,
+            usri3_max_storage: -1,
+            usri3_units_per_week: 0,
+            usri3_logon_hours: nil,
+            usri3_bad_pw_count: 0,
+            usri3_num_logons: 0,
+            usri3_logon_server: nil,
+            usri3_country_code: 0,
+            usri3_code_page: 0,
+            usri3_user_id: 0,
+            usri3_primary_group_id: DOMAIN_GROUP_RID_USERS,
+            usri3_profile: nil,
+            usri3_home_dir_drive: nil,
+            usri3_password_expired: 0
+          }.each do |(k,v)|
+            s.set(k, v)
+          end
+        end
+      end
+
+      def self.net_api_error!(code)
+        msg = case code
+        when NERR_InvalidComputer
+          "The user does not have access to the requested information."
+        when NERR_NotPrimary
+          "The operation is allowed only on the primary domain controller of the domain."
+        when NERR_SpeGroupOp
+          "This operation is not allowed on this special group."
+        when NERR_LastAdmin
+          "This operation is not allowed on the last administrative account."
+        when NERR_BadUsername
+          "The user name or group name parameter is invalid."
+        when NERR_BadPassword
+          "The password parameter is invalid."
+        when NERR_UserNotFound
+          raise Chef::Exceptions::UserIDNotFound, code
+        when NERR_PasswordTooShort
+          <<END
+The password is shorter than required. (The password could also be too
+long, be too recent in its change history, not have enough unique characters,
+or not meet another password policy requirement.)
+END
+        when NERR_GroupNotFound
+          "The group name could not be found."
+        when ERROR_ACCESS_DENIED
+          "The user does not have access to the requested information."
+        else
+          "Received unknown error code (#{code})"
+        end
+
+        raise Chef::Exceptions::Win32NetAPIError.new(msg, code)
+      end
+
+      def self.net_local_group_add(server_name, group_name)
+        server_name = wstring(server_name)
+        group_name = wstring(group_name)
+
+        buf = LOCALGROUP_INFO_0.new
+        buf[:lgrpi0_name] = FFI::MemoryPointer.from_string(group_name)
+
+        rc = NetLocalGroupAdd(server_name, 0, buf, nil)
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.net_local_group_del(server_name, group_name)
+        server_name = wstring(server_name)
+        group_name = wstring(group_name)
+
+        rc = NetLocalGroupDel(server_name, group_name)
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.net_local_group_get_members(server_name, group_name)
+        server_name = wstring(server_name)
+        group_name = wstring(group_name)
+
+        buf = FFI::MemoryPointer.new(:pointer)
+        entries_read_ptr = FFI::MemoryPointer.new(:long)
+        total_read_ptr = FFI::MemoryPointer.new(:long)
+        resume_handle_ptr = FFI::MemoryPointer.new(:pointer)
+
+        rc = ERROR_MORE_DATA
+        group_members = []
+        while rc == ERROR_MORE_DATA
+          rc = NetLocalGroupGetMembers(
+            server_name, group_name, 0, buf, -1,
+            entries_read_ptr, total_read_ptr, resume_handle_ptr
+          )
+
+          nread = entries_read_ptr.read_long
+          nread.times do |i|
+            member = LOCALGROUP_MEMBERS_INFO_0.new(buf.read_pointer +
+                       (i * LOCALGROUP_MEMBERS_INFO_0.size))
+            member_sid = Chef::ReservedNames::Win32::Security::SID.new(member[:lgrmi0_sid])
+            group_members << member_sid.to_s
+          end
+          NetApiBufferFree(buf.read_pointer)
+        end
+
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+
+        group_members
+      end
+
+      def self.net_user_add_l3(server_name, args)
+        buf = default_user_info_3
+
+        args.each do |k, v|
+          buf.set(k, v)
+        end
+
+        server_name = wstring(server_name)
+
+        rc = NetUserAdd(server_name, 3, buf, nil)
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.net_user_get_info_l3(server_name, user_name)
+        server_name = wstring(server_name)
+        user_name = wstring(user_name)
+
+        ui3_p = FFI::MemoryPointer.new(:pointer)
+
+        rc = NetUserGetInfo(server_name, user_name, 3, ui3_p)
+
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+
+        ui3 = USER_INFO_3.new(ui3_p.read_pointer).as_ruby
+
+        rc = NetApiBufferFree(ui3_p.read_pointer)
+
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+
+        ui3
+      end
+
+      def self.net_user_set_info_l3(server_name, user_name, info)
+        buf = default_user_info_3
+
+        info.each do |k, v|
+          buf.set(k, v)
+        end
+
+        server_name = wstring(server_name)
+        user_name = wstring(user_name)
+
+        rc = NetUserSetInfo(server_name, user_name, 3, buf, nil)
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.net_user_del(server_name, user_name)
+        server_name = wstring(server_name)
+        user_name = wstring(user_name)
+
+        rc = NetUserDel(server_name, user_name)
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.net_local_group_add_member(server_name, group_name, domain_user)
+        server_name = wstring(server_name)
+        group_name = wstring(group_name)
+        domain_user = wstring(domain_user)
+
+        buf = LOCALGROUP_MEMBERS_INFO_3.new
+        buf[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(domain_user)
+
+        rc = NetLocalGroupAddMembers(server_name, group_name, 3, buf, 1)
+
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.members_to_lgrmi3(members)
+        buf = FFI::MemoryPointer.new(LOCALGROUP_MEMBERS_INFO_3, members.size)
+        members.size.times.collect do |i|
+          member_info = LOCALGROUP_MEMBERS_INFO_3.new(
+            buf + i * LOCALGROUP_MEMBERS_INFO_3.size)
+          member_info[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(wstring(members[i]))
+          member_info
+        end
+      end
+
+      def self.net_local_group_add_members(server_name, group_name, members)
+        server_name = wstring(server_name)
+        group_name = wstring(group_name)
+
+        lgrmi3s = members_to_lgrmi3(members)
+        rc = NetLocalGroupAddMembers(
+          server_name, group_name, 3, lgrmi3s[0], members.size)
+
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.net_local_group_set_members(server_name, group_name, members)
+        server_name = wstring(server_name)
+        group_name = wstring(group_name)
+
+        lgrmi3s = members_to_lgrmi3(members)
+        rc = NetLocalGroupSetMembers(
+          server_name, group_name, 3, lgrmi3s[0], members.size)
+
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.net_local_group_del_members(server_name, group_name, members)
+        server_name = wstring(server_name)
+        group_name = wstring(group_name)
+
+        lgrmi3s = members_to_lgrmi3(members)
+        rc = NetLocalGroupDelMembers(
+          server_name, group_name, 3, lgrmi3s[0], members.size)
+
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.net_use_del(server_name, use_name, force=:use_noforce)
+        server_name = wstring(server_name)
+        use_name = wstring(use_name)
+        force_const = case force
+                      when :use_noforce
+                        USE_NOFORCE
+                      when :use_force
+                        USE_FORCE
+                      when :use_lots_of_force
+                        USE_LOTS_OF_FORCE
+                      else
+                        raise ArgumentError, "force must be one of [:use_noforce, :use_force, or :use_lots_of_force]"
+                      end
+
+        rc = NetUseDel(server_name, use_name, force_const)
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+
+      def self.net_use_get_info_l2(server_name, use_name)
+        server_name = wstring(server_name)
+        use_name = wstring(use_name)
+        ui2_p = FFI::MemoryPointer.new(:pointer)
+
+        rc = NetUseGetInfo(server_name, use_name, 2, ui2_p)
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+
+        ui2 = USE_INFO_2.new(ui2_p.read_pointer).as_ruby
+        NetApiBufferFree(ui2_p.read_pointer)
+
+        ui2
+      end
+
+      def self.net_use_add_l2(server_name, ui2_hash)
+        server_name = wstring(server_name)
+        group_name = wstring(group_name)
+
+        buf = USE_INFO_2.new
+
+        ui2_hash.each do |(k,v)|
+          buf.set(k,v)
+        end
+
+        rc = NetUseAdd(server_name, 2, buf, nil)
+        if rc != NERR_Success
+          net_api_error!(rc)
+        end
+      end
+    end
+    NetUser = Net # For backwards compatibility
+  end
+end
diff --git a/lib/chef/win32/process.rb b/lib/chef/win32/process.rb
index 2df39bb..767d4f3 100644
--- a/lib/chef/win32/process.rb
+++ b/lib/chef/win32/process.rb
@@ -69,6 +69,19 @@ class Chef
         result
       end
 
+      def self.is_wow64_process
+        is_64_bit_process_result = FFI::MemoryPointer.new(:int)
+
+        # The return value of IsWow64Process is nonzero value if the API call succeeds.
+        # The result data are returned in the last parameter, not the return value.
+        call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result)
+
+        # The result is nonzero if IsWow64Process's calling process, in the case here
+        # this process, is running under WOW64, i.e. the result is nonzero if this
+        # process is 32-bit (aka :i386).
+        (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0)
+      end
+
         # Must have PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION rights,
         # AND the PROCESS_VM_READ right
       def self.get_process_memory_info(handle)
diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb
index 18f12d2..12ad08a 100644
--- a/lib/chef/win32/registry.rb
+++ b/lib/chef/win32/registry.rb
@@ -17,8 +17,12 @@
 # limitations under the License.
 #
 require 'chef/reserved_names'
+require 'chef/win32/api'
+require 'chef/mixin/wide_string'
 
 if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+  require 'chef/monkey_patches/win32/registry'
+  require 'chef/win32/api/registry'
   require 'win32/registry'
   require 'win32/api'
 end
@@ -27,6 +31,14 @@ class Chef
   class Win32
     class Registry
 
+      if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+        include Chef::ReservedNames::Win32::API::Registry
+        extend Chef::ReservedNames::Win32::API::Registry
+      end
+
+      include Chef::Mixin::WideString
+      extend Chef::Mixin::WideString
+
       attr_accessor :run_context
       attr_accessor :architecture
 
@@ -115,38 +127,23 @@ class Chef
           Chef::Log.debug("Registry key #{key_path}, does not exist, not deleting")
           return true
         end
-        #key_path is in the form "HKLM\Software\Opscode" for example, extracting
-        #hive = HKLM,
-        #hive_namespace = ::Win32::Registry::HKEY_LOCAL_MACHINE
-        hive = key_path.split("\\").shift
-        hive_namespace, key_including_parent = get_hive_and_key(key_path)
-        if has_subkeys?(key_path)
-          if recursive == true
-            subkeys = get_subkeys(key_path)
-            subkeys.each do |key|
-              keypath_to_check = hive+"\\"+key_including_parent+"\\"+key
-              Chef::Log.debug("Deleting registry key #{key_path} recursively")
-              delete_key(keypath_to_check, true)
-            end
-            delete_key_ex(hive_namespace, key_including_parent)
-          else
-            raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has subkeys, and recursive not specified"
-          end
-        else
-          delete_key_ex(hive_namespace, key_including_parent)
-          return true
+        if has_subkeys?(key_path) && !recursive
+          raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has subkeys, and recursive not specified"
+        end
+        hive, key_including_parent = get_hive_and_key(key_path)
+        # key_including_parent: Software\\Root\\Branch\\Fruit
+        # key => Fruit
+        # key_parent => Software\\Root\\Branch
+        key_parts = key_including_parent.split("\\")
+        key = key_parts.pop
+        key_parent = key_parts.join("\\")
+        hive.open(key_parent, ::Win32::Registry::KEY_WRITE | registry_system_architecture) do |reg|
+          reg.delete_key(key, recursive)
         end
+        Chef::Log.debug("Registry key #{key_path} deleted")
         true
       end
 
-      #Using the 'RegDeleteKeyEx' Windows API that correctly supports WOW64 systems (Win2003)
-      #instead of the 'RegDeleteKey'
-      def delete_key_ex(hive, key)
-        regDeleteKeyEx = ::Win32::API.new('RegDeleteKeyEx', 'LPLL', 'L', 'advapi32')
-        hive_num = hive.hkey - (1 << 32)
-        regDeleteKeyEx.call(hive_num, key, ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0)
-      end
-
       def key_exists?(key_path)
         hive, key = get_hive_and_key(key_path)
         begin
@@ -203,7 +200,7 @@ class Chef
         key_exists!(key_path)
         hive, key = get_hive_and_key(key_path)
         hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
-          return true if reg.any? {|val| val == value[:name] }
+          return true if reg.any? {|val| safely_downcase(val) == safely_downcase(value[:name]) }
         end
         return false
       end
@@ -213,7 +210,7 @@ class Chef
         hive, key = get_hive_and_key(key_path)
         hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
           reg.each do |val_name, val_type, val_data|
-            if val_name == value[:name] &&
+            if safely_downcase(val_name) == safely_downcase(value[:name]) &&
               val_type == get_type_from_name(value[:type]) &&
               val_data == value[:data]
               return true
@@ -289,6 +286,14 @@ class Chef
 
       private
 
+
+      def safely_downcase(val)
+        if val.is_a? String
+          return val.downcase
+        end
+        return val
+      end
+
       def node
         run_context && run_context.node
       end
diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb
index 3902d8c..bc80517 100644
--- a/lib/chef/win32/security.rb
+++ b/lib/chef/win32/security.rb
@@ -22,6 +22,7 @@ require 'chef/win32/memory'
 require 'chef/win32/process'
 require 'chef/win32/unicode'
 require 'chef/win32/security/token'
+require 'chef/mixin/wide_string'
 
 class Chef
   module ReservedNames::Win32
@@ -31,6 +32,8 @@ class Chef
       include Chef::ReservedNames::Win32::API::Security
       extend Chef::ReservedNames::Win32::API::Security
       extend Chef::ReservedNames::Win32::API::Macros
+      include Chef::Mixin::WideString
+      extend Chef::Mixin::WideString
 
       def self.access_check(security_descriptor, token, desired_access, generic_mapping)
         token_handle = token.handle.handle
@@ -270,6 +273,36 @@ class Chef
         [ present.read_char != 0, acl.null? ? nil : ACL.new(acl, security_descriptor), defaulted.read_char != 0 ]
       end
 
+      def self.get_token_information_owner(token)
+        owner_result_size = FFI::MemoryPointer.new(:ulong)
+        if GetTokenInformation(token.handle.handle, :TokenOwner, nil, 0, owner_result_size)
+          raise "Expected ERROR_INSUFFICIENT_BUFFER from GetTokenInformation, and got no error!"
+        elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+        owner_result_storage = FFI::MemoryPointer.new owner_result_size.read_ulong
+        unless GetTokenInformation(token.handle.handle, :TokenOwner, owner_result_storage, owner_result_size.read_ulong, owner_result_size)
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+        owner_result = TOKEN_OWNER.new owner_result_storage
+        SID.new(owner_result[:Owner], owner_result_storage)
+      end
+
+      def self.get_token_information_primary_group(token)
+        group_result_size = FFI::MemoryPointer.new(:ulong)
+        if GetTokenInformation(token.handle.handle, :TokenPrimaryGroup, nil, 0, group_result_size)
+          raise "Expected ERROR_INSUFFICIENT_BUFFER from GetTokenInformation, and got no error!"
+        elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+        group_result_storage = FFI::MemoryPointer.new group_result_size.read_ulong
+        unless GetTokenInformation(token.handle.handle, :TokenPrimaryGroup, group_result_storage, group_result_size.read_ulong, group_result_size)
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+        group_result = TOKEN_PRIMARY_GROUP.new group_result_storage
+        SID.new(group_result[:PrimaryGroup], group_result_storage)
+      end
+
       def self.initialize_acl(acl_size)
         acl = FFI::MemoryPointer.new acl_size
         unless InitializeAcl(acl, acl_size, ACL_REVISION)
@@ -415,6 +448,10 @@ class Chef
         [ SecurityDescriptor.new(absolute_sd), SID.new(owner), SID.new(group), ACL.new(dacl), ACL.new(sacl) ]
       end
 
+      def self.open_current_process_token(desired_access = TOKEN_READ)
+        open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, desired_access)
+      end
+
       def self.open_process_token(process, desired_access)
         process = process.handle if process.respond_to?(:handle)
         process = process.handle if process.respond_to?(:handle)
@@ -513,7 +550,7 @@ class Chef
 
       def self.with_privileges(*privilege_names)
         # Set privileges
-        token = open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, TOKEN_READ | TOKEN_ADJUST_PRIVILEGES)
+        token = open_current_process_token(TOKEN_READ | TOKEN_ADJUST_PRIVILEGES)
         old_privileges = token.enable_privileges(*privilege_names)
 
         # Let the caller do their privileged stuff
@@ -533,7 +570,7 @@ class Chef
 
           true
         else
-          process_token = open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, TOKEN_READ)
+          process_token = open_current_process_token(TOKEN_READ)
           elevation_result = FFI::Buffer.new(:ulong)
           elevation_result_size = FFI::MemoryPointer.new(:uint32)
           success = GetTokenInformation(process_token.handle.handle, :TokenElevation, elevation_result, 4, elevation_result_size)
@@ -543,6 +580,18 @@ class Chef
           success && (elevation_result.read_ulong != 0)
         end
       end
+
+      def self.logon_user(username, domain, password, logon_type, logon_provider)
+        username = wstring(username)
+        domain = wstring(domain)
+        password = wstring(password)
+
+        token = FFI::Buffer.new(:pointer)
+        unless LogonUserW(username, domain, password, logon_type, logon_provider, token)
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+        Token.new(Handle.new(token.read_pointer))
+      end
     end
   end
 end
diff --git a/lib/chef/win32/security/sid.rb b/lib/chef/win32/security/sid.rb
index 8e9407d..f8bd934 100644
--- a/lib/chef/win32/security/sid.rb
+++ b/lib/chef/win32/security/sid.rb
@@ -203,6 +203,23 @@ class Chef
           SID.from_account("#{::ENV['USERDOMAIN']}\\#{::ENV['USERNAME']}")
         end
 
+        # See https://technet.microsoft.com/en-us/library/cc961992.aspx
+        # In practice, this is SID.Administrators if the current_user is an admin (even if not
+        # running elevated), and is current_user otherwise. On win2k3, it technically can be
+        # current_user in all cases if a certain group policy is set.
+        def self.default_security_object_owner
+          token = Chef::ReservedNames::Win32::Security.open_current_process_token
+          Chef::ReservedNames::Win32::Security.get_token_information_owner(token)
+        end
+
+        # See https://technet.microsoft.com/en-us/library/cc961996.aspx
+        # In practice, this seems to be SID.current_user for Microsoft Accounts, the current
+        # user's Domain Users group for domain accounts, and SID.None otherwise.
+        def self.default_security_object_group
+          token = Chef::ReservedNames::Win32::Security.open_current_process_token
+          Chef::ReservedNames::Win32::Security.get_token_information_primary_group(token)
+        end
+
         def self.admin_account_name
           @admin_account_name ||= begin
             admin_account_name = nil
diff --git a/lib/chef/win32/security/token.rb b/lib/chef/win32/security/token.rb
index 9e494a7..8d4e54a 100644
--- a/lib/chef/win32/security/token.rb
+++ b/lib/chef/win32/security/token.rb
@@ -18,7 +18,7 @@
 
 require 'chef/win32/security'
 require 'chef/win32/api/security'
-
+require 'chef/win32/unicode'
 require 'ffi'
 
 class Chef
diff --git a/lib/chef/win32/system.rb b/lib/chef/win32/system.rb
new file mode 100755
index 0000000..cdd063f
--- /dev/null
+++ b/lib/chef/win32/system.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Salim Alam (<salam at chef.io>)
+# Copyright:: Copyright 2015 Opscode, 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/win32/api/system'
+require 'chef/win32/error'
+require 'ffi'
+
+class Chef
+  module ReservedNames::Win32
+    class System
+      include Chef::ReservedNames::Win32::API::System
+      extend Chef::ReservedNames::Win32::API::System
+
+      def self.get_system_wow64_directory
+        ptr = FFI::MemoryPointer.new(:char, 255, true)
+        succeeded = GetSystemWow64DirectoryA(ptr, 255)
+
+        if succeeded == 0
+          raise Win32APIError, "Failed to get Wow64 system directory"
+        end
+
+        ptr.read_string.strip
+      end
+
+      def self.wow64_disable_wow64_fs_redirection
+        original_redirection_state = FFI::MemoryPointer.new(:pointer)
+
+        succeeded = Wow64DisableWow64FsRedirection(original_redirection_state)
+
+        if succeeded == 0
+          raise Win32APIError, "Failed to disable Wow64 file redirection"
+        end
+
+        original_redirection_state
+      end
+
+      def self.wow64_revert_wow64_fs_redirection(original_redirection_state)
+        succeeded = Wow64RevertWow64FsRedirection(original_redirection_state)
+
+        if succeeded == 0
+          raise Win32APIError, "Failed to revert Wow64 file redirection"
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/win32/unicode.rb b/lib/chef/win32/unicode.rb
index e7399d5..d63b979 100644
--- a/lib/chef/win32/unicode.rb
+++ b/lib/chef/win32/unicode.rb
@@ -17,6 +17,7 @@
 # limitations under the License.
 #
 
+require 'chef/mixin/wide_string'
 require 'chef/win32/api/unicode'
 
 class Chef
@@ -30,6 +31,8 @@ end
 
 module FFI
   class Pointer
+    include Chef::Mixin::WideString
+
     def read_wstring(num_wchars = nil)
       if num_wchars.nil?
         # Find the length of the string
@@ -43,13 +46,15 @@ module FFI
         num_wchars = length
       end
 
-      Chef::ReservedNames::Win32::Unicode.wide_to_utf8(self.get_bytes(0, num_wchars*2))
+      wide_to_utf8(self.get_bytes(0, num_wchars*2))
     end
   end
 end
 
 class String
+  include Chef::Mixin::WideString
+
   def to_wstring
-    Chef::ReservedNames::Win32::Unicode.utf8_to_wide(self)
+    utf8_to_wide(self)
   end
 end
diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb
index 17c27e4..6a7a65b 100644
--- a/lib/chef/win32/version.rb
+++ b/lib/chef/win32/version.rb
@@ -122,10 +122,6 @@ class Chef
         # WMI always returns the truth. See article at
         # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
 
-        # CHEF-4888: Work around ruby #2618, expected to be fixed in Ruby 2.1.0
-        # https://github.com/ruby/ruby/commit/588504b20f5cc880ad51827b93e571e32446e5db
-        # https://github.com/ruby/ruby/commit/27ed294c7134c0de582007af3c915a635a6506cd
-
         wmi = WmiLite::Wmi.new
         os_info = wmi.first_of('Win32_OperatingSystem')
         os_version = os_info['version']
diff --git a/lib/chef/workstation_config_loader.rb b/lib/chef/workstation_config_loader.rb
index 2454c9c..8398c5d 100644
--- a/lib/chef/workstation_config_loader.rb
+++ b/lib/chef/workstation_config_loader.rb
@@ -1,5 +1,5 @@
 #
-# Author:: Daniel DeLeo (<dan at getchef.com>)
+# Author:: Claire McQuin (<claire at chef.io>)
 # Copyright:: Copyright (c) 2014 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -16,163 +16,8 @@
 # limitations under the License.
 #
 
-require 'chef/config_fetcher'
-require 'chef/config'
-require 'chef/null_logger'
-require 'chef/util/path_helper'
+require 'chef-config/workstation_config_loader'
 
 class Chef
-
-  class WorkstationConfigLoader
-
-    # Path to a config file requested by user, (e.g., via command line option). Can be nil
-    attr_accessor :explicit_config_file
-
-    # TODO: initialize this with a logger for Chef and Knife
-    def initialize(explicit_config_file, logger=nil)
-      @explicit_config_file = explicit_config_file
-      @config_location = nil
-      @logger = logger || NullLogger.new
-    end
-
-    def no_config_found?
-      config_location.nil?
-    end
-
-    def config_location
-      @config_location ||= (explicit_config_file || locate_local_config)
-    end
-
-    def chef_config_dir
-      if @chef_config_dir.nil?
-        @chef_config_dir = false
-        full_path = working_directory.split(File::SEPARATOR)
-        (full_path.length - 1).downto(0) do |i|
-          candidate_directory = File.join(full_path[0..i] + [".chef" ])
-          if File.exist?(candidate_directory) && File.directory?(candidate_directory)
-            @chef_config_dir = candidate_directory
-            break
-          end
-        end
-      end
-      @chef_config_dir
-    end
-
-    def load
-      # Ignore it if there's no explicit_config_file and can't find one at a
-      # default path.
-      return false if config_location.nil?
-
-      if explicit_config_file && !path_exists?(config_location)
-        raise Exceptions::ConfigurationError, "Specified config file #{config_location} does not exist"
-      end
-
-      # Have to set Chef::Config.config_file b/c other config is derived from it.
-      Chef::Config.config_file = config_location
-      read_config(IO.read(config_location), config_location)
-    end
-
-    # (Private API, public for test purposes)
-    def env
-      ENV
-    end
-
-    # (Private API, public for test purposes)
-    def path_exists?(path)
-      Pathname.new(path).expand_path.exist?
-    end
-
-    private
-
-    def have_config?(path)
-      if path_exists?(path)
-        logger.info("Using config at #{path}")
-        true
-      else
-        logger.debug("Config not found at #{path}, trying next option")
-        false
-      end
-    end
-
-    def locate_local_config
-      candidate_configs = []
-
-      # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
-      if env['KNIFE_HOME']
-        candidate_configs << File.join(env['KNIFE_HOME'], 'config.rb')
-        candidate_configs << File.join(env['KNIFE_HOME'], 'knife.rb')
-      end
-      # Look for $PWD/knife.rb
-      if Dir.pwd
-        candidate_configs << File.join(Dir.pwd, 'config.rb')
-        candidate_configs << File.join(Dir.pwd, 'knife.rb')
-      end
-      # Look for $UPWARD/.chef/knife.rb
-      if chef_config_dir
-        candidate_configs << File.join(chef_config_dir, 'config.rb')
-        candidate_configs << File.join(chef_config_dir, 'knife.rb')
-      end
-      # Look for $HOME/.chef/knife.rb
-      Chef::Util::PathHelper.home('.chef') do |dot_chef_dir|
-        candidate_configs << File.join(dot_chef_dir, 'config.rb')
-        candidate_configs << File.join(dot_chef_dir, 'knife.rb')
-      end
-
-      candidate_configs.find do | candidate_config |
-        have_config?(candidate_config)
-      end
-    end
-
-    def working_directory
-      a = if Chef::Platform.windows?
-            env['CD']
-          else
-            env['PWD']
-          end || Dir.pwd
-
-      a
-    end
-
-    def read_config(config_content, config_file_path)
-      Chef::Config.from_string(config_content, config_file_path)
-    rescue SignalException
-      raise
-    rescue SyntaxError => e
-      message = ""
-      message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n"
-      message << "#{e.class.name}: #{e.message}\n"
-      if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/]
-        line = file_line[/:([\d]+)$/, 1].to_i
-        message << highlight_config_error(config_file_path, line)
-      end
-      raise Exceptions::ConfigurationError, message
-    rescue Exception => e
-      message = "You have an error in your config file #{config_file_path}\n\n"
-      message << "#{e.class.name}: #{e.message}\n"
-      filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
-      filtered_trace.each {|bt_line| message << "  " << bt_line << "\n" }
-      if !filtered_trace.empty?
-        line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1]
-        message << highlight_config_error(config_file_path, line_nr.to_i)
-      end
-      raise Exceptions::ConfigurationError, message
-    end
-
-
-    def highlight_config_error(file, line)
-      config_file_lines = []
-      IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"}
-      if line == 1
-        lines = config_file_lines[0..3]
-      else
-        lines = config_file_lines[Range.new(line - 2, line)]
-      end
-      "Relevant file content:\n" + lines.join("\n") + "\n"
-    end
-
-    def logger
-      @logger
-    end
-
-  end
+  WorkstationConfigLoader = ChefConfig::WorkstationConfigLoader
 end
diff --git a/metadata.yml b/metadata.yml
index 4bb3d01..7169283 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,29 +1,29 @@
 --- !ruby/object:Gem::Specification
 name: chef
 version: !ruby/object:Gem::Version
-  version: 12.3.0
+  version: 12.6.0
 platform: ruby
 authors:
 - Adam Jacob
 autorequire: 
 bindir: bin
 cert_chain: []
-date: 2015-04-28 00:00:00.000000000 Z
+date: 2015-12-21 00:00:00.000000000 Z
 dependencies:
 - !ruby/object:Gem::Dependency
-  name: mixlib-config
+  name: chef-config
   requirement: !ruby/object:Gem::Requirement
     requirements:
-    - - "~>"
+    - - '='
       - !ruby/object:Gem::Version
-        version: '2.0'
+        version: 12.6.0
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
-    - - "~>"
+    - - '='
       - !ruby/object:Gem::Version
-        version: '2.0'
+        version: 12.6.0
 - !ruby/object:Gem::Dependency
   name: mixlib-cli
   requirement: !ruby/object:Gem::Requirement
@@ -70,56 +70,50 @@ dependencies:
   name: mixlib-shellout
   requirement: !ruby/object:Gem::Requirement
     requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 2.0.0.rc.0
-    - - "<"
+    - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.0'
+        version: '2.0'
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 2.0.0.rc.0
-    - - "<"
+    - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.0'
+        version: '2.0'
 - !ruby/object:Gem::Dependency
   name: ohai
   requirement: !ruby/object:Gem::Requirement
     requirements:
-    - - "~>"
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: 8.6.0.alpha.1
+    - - "<"
       - !ruby/object:Gem::Version
-        version: '8.0'
+        version: '9'
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
-    - - "~>"
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: 8.6.0.alpha.1
+    - - "<"
       - !ruby/object:Gem::Version
-        version: '8.0'
+        version: '9'
 - !ruby/object:Gem::Dependency
   name: ffi-yajl
   requirement: !ruby/object:Gem::Requirement
     requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '1.2'
-    - - "<"
+    - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.0'
+        version: '2.2'
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '1.2'
-    - - "<"
+    - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.0'
+        version: '2.2'
 - !ruby/object:Gem::Dependency
   name: net-ssh
   requirement: !ruby/object:Gem::Requirement
@@ -208,14 +202,20 @@ dependencies:
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: '4.1'
+        version: '4.2'
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: 4.2.2
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: '4.1'
+        version: '4.2'
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: 4.2.2
 - !ruby/object:Gem::Dependency
   name: pry
   requirement: !ruby/object:Gem::Requirement
@@ -250,42 +250,42 @@ dependencies:
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.2'
+        version: '3.4'
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.2'
+        version: '3.4'
 - !ruby/object:Gem::Dependency
   name: rspec-expectations
   requirement: !ruby/object:Gem::Requirement
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.2'
+        version: '3.4'
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.2'
+        version: '3.4'
 - !ruby/object:Gem::Dependency
   name: rspec-mocks
   requirement: !ruby/object:Gem::Requirement
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.2'
+        version: '3.4'
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: '3.2'
+        version: '3.4'
 - !ruby/object:Gem::Dependency
   name: rspec_junit_formatter
   requirement: !ruby/object:Gem::Requirement
@@ -329,6 +329,34 @@ dependencies:
       - !ruby/object:Gem::Version
         version: '2.10'
 - !ruby/object:Gem::Dependency
+  name: syslog-logger
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '1.6'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '1.6'
+- !ruby/object:Gem::Dependency
+  name: proxifier
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '1.0'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '1.0'
+- !ruby/object:Gem::Dependency
   name: rack
   requirement: !ruby/object:Gem::Requirement
     requirements:
@@ -343,22 +371,36 @@ dependencies:
       - !ruby/object:Gem::Version
         version: '0'
 - !ruby/object:Gem::Dependency
+  name: cheffish
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '1.1'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - "~>"
+      - !ruby/object:Gem::Version
+        version: '1.1'
+- !ruby/object:Gem::Dependency
   name: rake
   requirement: !ruby/object:Gem::Requirement
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: 10.1.0
+        version: '10.1'
   type: :development
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
     - - "~>"
       - !ruby/object:Gem::Version
-        version: 10.1.0
+        version: '10.1'
 description: A systems integration framework, built to bring the benefits of configuration
   management to your entire infrastructure.
-email: adam at getchef.com
+email: adam at chef.io
 executables:
 - chef-client
 - chef-solo
@@ -372,6 +414,7 @@ extra_rdoc_files:
 - LICENSE
 files:
 - CONTRIBUTING.md
+- Gemfile
 - LICENSE
 - README.md
 - Rakefile
@@ -380,6 +423,8 @@ files:
 - bin/chef-shell
 - bin/chef-solo
 - bin/knife
+- chef-windows.gemspec
+- chef.gemspec
 - distro/common/html/_sources/ctl_chef_client.txt
 - distro/common/html/_sources/ctl_chef_server.txt
 - distro/common/html/_sources/ctl_chef_shell.txt
@@ -543,9 +588,11 @@ files:
 - distro/common/markdown/man8/chef-server.mkd
 - distro/common/markdown/man8/chef-solo.mkd
 - distro/common/markdown/man8/chef-solr.mkd
+- distro/powershell/chef/chef.psm1
 - lib/chef.rb
 - lib/chef/api_client.rb
 - lib/chef/api_client/registration.rb
+- lib/chef/api_client_v1.rb
 - lib/chef/application.rb
 - lib/chef/application/apply.rb
 - lib/chef/application/client.rb
@@ -557,6 +604,7 @@ files:
 - lib/chef/audit/audit_event_proxy.rb
 - lib/chef/audit/audit_reporter.rb
 - lib/chef/audit/control_group_data.rb
+- lib/chef/audit/logger.rb
 - lib/chef/audit/rspec_formatter.rb
 - lib/chef/audit/runner.rb
 - lib/chef/chef_class.rb
@@ -632,6 +680,7 @@ files:
 - lib/chef/client.rb
 - lib/chef/config.rb
 - lib/chef/config_fetcher.rb
+- lib/chef/constants.rb
 - lib/chef/cookbook/chefignore.rb
 - lib/chef/cookbook/cookbook_collection.rb
 - lib/chef/cookbook/cookbook_version_loader.rb
@@ -649,16 +698,22 @@ files:
 - lib/chef/daemon.rb
 - lib/chef/data_bag.rb
 - lib/chef/data_bag_item.rb
+- lib/chef/delayed_evaluator.rb
 - lib/chef/deprecation/mixin/template.rb
 - lib/chef/deprecation/provider/cookbook_file.rb
 - lib/chef/deprecation/provider/file.rb
+- lib/chef/deprecation/provider/remote_directory.rb
 - lib/chef/deprecation/provider/remote_file.rb
 - lib/chef/deprecation/provider/template.rb
 - lib/chef/deprecation/warnings.rb
 - lib/chef/digester.rb
 - lib/chef/dsl.rb
 - lib/chef/dsl/audit.rb
+- lib/chef/dsl/chef_provisioning.rb
+- lib/chef/dsl/cheffish.rb
 - lib/chef/dsl/data_query.rb
+- lib/chef/dsl/declare_resource.rb
+- lib/chef/dsl/definitions.rb
 - lib/chef/dsl/include_attribute.rb
 - lib/chef/dsl/include_recipe.rb
 - lib/chef/dsl/platform_introspection.rb
@@ -666,6 +721,7 @@ files:
 - lib/chef/dsl/reboot_pending.rb
 - lib/chef/dsl/recipe.rb
 - lib/chef/dsl/registry_helper.rb
+- lib/chef/dsl/resources.rb
 - lib/chef/encrypted_data_bag_item.rb
 - lib/chef/encrypted_data_bag_item/assertions.rb
 - lib/chef/encrypted_data_bag_item/check_encrypted.rb
@@ -680,6 +736,7 @@ files:
 - lib/chef/environment.rb
 - lib/chef/event_dispatch/base.rb
 - lib/chef/event_dispatch/dispatcher.rb
+- lib/chef/event_dispatch/dsl.rb
 - lib/chef/event_dispatch/events_output_stream.rb
 - lib/chef/event_loggers/base.rb
 - lib/chef/event_loggers/windows_eventlog.rb
@@ -732,18 +789,22 @@ files:
 - lib/chef/http/ssl_policies.rb
 - lib/chef/http/validate_content_length.rb
 - lib/chef/json_compat.rb
+- lib/chef/key.rb
 - lib/chef/knife.rb
 - lib/chef/knife/bootstrap.rb
 - lib/chef/knife/bootstrap/chef_vault_handler.rb
 - lib/chef/knife/bootstrap/client_builder.rb
 - lib/chef/knife/bootstrap/templates/README.md
-- lib/chef/knife/bootstrap/templates/archlinux-gems.erb
-- lib/chef/knife/bootstrap/templates/chef-aix.erb
 - lib/chef/knife/bootstrap/templates/chef-full.erb
 - lib/chef/knife/client_bulk_delete.rb
 - lib/chef/knife/client_create.rb
 - lib/chef/knife/client_delete.rb
 - lib/chef/knife/client_edit.rb
+- lib/chef/knife/client_key_create.rb
+- lib/chef/knife/client_key_delete.rb
+- lib/chef/knife/client_key_edit.rb
+- lib/chef/knife/client_key_list.rb
+- lib/chef/knife/client_key_show.rb
 - lib/chef/knife/client_list.rb
 - lib/chef/knife/client_reregister.rb
 - lib/chef/knife/client_show.rb
@@ -769,7 +830,10 @@ files:
 - lib/chef/knife/cookbook_upload.rb
 - lib/chef/knife/core/bootstrap_context.rb
 - lib/chef/knife/core/cookbook_scm_repo.rb
+- lib/chef/knife/core/custom_manifest_loader.rb
+- lib/chef/knife/core/gem_glob_loader.rb
 - lib/chef/knife/core/generic_presenter.rb
+- lib/chef/knife/core/hashed_command_loader.rb
 - lib/chef/knife/core/node_editor.rb
 - lib/chef/knife/core/node_presenter.rb
 - lib/chef/knife/core/object_loader.rb
@@ -800,6 +864,14 @@ files:
 - lib/chef/knife/help.rb
 - lib/chef/knife/help_topics.rb
 - lib/chef/knife/index_rebuild.rb
+- lib/chef/knife/key_create.rb
+- lib/chef/knife/key_create_base.rb
+- lib/chef/knife/key_delete.rb
+- lib/chef/knife/key_edit.rb
+- lib/chef/knife/key_edit_base.rb
+- lib/chef/knife/key_list.rb
+- lib/chef/knife/key_list_base.rb
+- lib/chef/knife/key_show.rb
 - lib/chef/knife/list.rb
 - lib/chef/knife/node_bulk_delete.rb
 - lib/chef/knife/node_create.rb
@@ -812,8 +884,16 @@ files:
 - lib/chef/knife/node_run_list_remove.rb
 - lib/chef/knife/node_run_list_set.rb
 - lib/chef/knife/node_show.rb
+- lib/chef/knife/null.rb
+- lib/chef/knife/osc_user_create.rb
+- lib/chef/knife/osc_user_delete.rb
+- lib/chef/knife/osc_user_edit.rb
+- lib/chef/knife/osc_user_list.rb
+- lib/chef/knife/osc_user_reregister.rb
+- lib/chef/knife/osc_user_show.rb
 - lib/chef/knife/raw.rb
 - lib/chef/knife/recipe_list.rb
+- lib/chef/knife/rehash.rb
 - lib/chef/knife/role_bulk_delete.rb
 - lib/chef/knife/role_create.rb
 - lib/chef/knife/role_delete.rb
@@ -845,13 +925,21 @@ files:
 - lib/chef/knife/user_create.rb
 - lib/chef/knife/user_delete.rb
 - lib/chef/knife/user_edit.rb
+- lib/chef/knife/user_key_create.rb
+- lib/chef/knife/user_key_delete.rb
+- lib/chef/knife/user_key_edit.rb
+- lib/chef/knife/user_key_list.rb
+- lib/chef/knife/user_key_show.rb
 - lib/chef/knife/user_list.rb
 - lib/chef/knife/user_reregister.rb
 - lib/chef/knife/user_show.rb
 - lib/chef/knife/xargs.rb
 - lib/chef/local_mode.rb
 - lib/chef/log.rb
+- lib/chef/log/syslog.rb
+- lib/chef/log/winevt.rb
 - lib/chef/mash.rb
+- lib/chef/mixin/api_version_request_handling.rb
 - lib/chef/mixin/checksum.rb
 - lib/chef/mixin/command.rb
 - lib/chef/mixin/command/unix.rb
@@ -871,20 +959,29 @@ files:
 - lib/chef/mixin/language_include_recipe.rb
 - lib/chef/mixin/params_validate.rb
 - lib/chef/mixin/path_sanity.rb
+- lib/chef/mixin/powershell_out.rb
 - lib/chef/mixin/powershell_type_coercions.rb
+- lib/chef/mixin/properties.rb
 - lib/chef/mixin/provides.rb
+- lib/chef/mixin/proxified_socket.rb
 - lib/chef/mixin/recipe_definition_dsl_core.rb
 - lib/chef/mixin/securable.rb
 - lib/chef/mixin/shell_out.rb
+- lib/chef/mixin/subclass_directive.rb
 - lib/chef/mixin/template.rb
+- lib/chef/mixin/unformatter.rb
+- lib/chef/mixin/uris.rb
 - lib/chef/mixin/which.rb
 - lib/chef/mixin/why_run.rb
+- lib/chef/mixin/wide_string.rb
 - lib/chef/mixin/windows_architecture_helper.rb
 - lib/chef/mixin/windows_env_helper.rb
 - lib/chef/mixin/xml_escape.rb
 - lib/chef/mixins.rb
 - lib/chef/monkey_patches/net-ssh-multi.rb
 - lib/chef/monkey_patches/net_http.rb
+- lib/chef/monkey_patches/webrick-utils.rb
+- lib/chef/monkey_patches/win32/registry.rb
 - lib/chef/monologger.rb
 - lib/chef/nil_argument.rb
 - lib/chef/node.rb
@@ -895,15 +992,21 @@ files:
 - lib/chef/null_logger.rb
 - lib/chef/org.rb
 - lib/chef/platform.rb
+- lib/chef/platform/handler_map.rb
+- lib/chef/platform/priority_map.rb
+- lib/chef/platform/provider_handler_map.rb
 - lib/chef/platform/provider_mapping.rb
 - lib/chef/platform/provider_priority_map.rb
 - lib/chef/platform/query_helpers.rb
 - lib/chef/platform/rebooter.rb
+- lib/chef/platform/resource_handler_map.rb
 - lib/chef/platform/resource_priority_map.rb
 - lib/chef/platform/service_helpers.rb
 - lib/chef/policy_builder.rb
+- lib/chef/policy_builder/dynamic.rb
 - lib/chef/policy_builder/expand_node_object.rb
 - lib/chef/policy_builder/policyfile.rb
+- lib/chef/property.rb
 - lib/chef/provider.rb
 - lib/chef/provider/batch.rb
 - lib/chef/provider/breakpoint.rb
@@ -972,7 +1075,9 @@ files:
 - lib/chef/provider/package/smartos.rb
 - lib/chef/provider/package/solaris.rb
 - lib/chef/provider/package/windows.rb
+- lib/chef/provider/package/windows/exe.rb
 - lib/chef/provider/package/windows/msi.rb
+- lib/chef/provider/package/windows/registry_uninstall_entry.rb
 - lib/chef/provider/package/yum-dump.py
 - lib/chef/provider/package/yum.rb
 - lib/chef/provider/package/zypper.rb
@@ -987,6 +1092,7 @@ files:
 - lib/chef/provider/remote_file/ftp.rb
 - lib/chef/provider/remote_file/http.rb
 - lib/chef/provider/remote_file/local_file.rb
+- lib/chef/provider/remote_file/network_file.rb
 - lib/chef/provider/resource_update.rb
 - lib/chef/provider/route.rb
 - lib/chef/provider/ruby_block.rb
@@ -1028,6 +1134,7 @@ files:
 - lib/chef/request_id.rb
 - lib/chef/reserved_names.rb
 - lib/chef/resource.rb
+- lib/chef/resource/action_class.rb
 - lib/chef/resource/apt_package.rb
 - lib/chef/resource/bash.rb
 - lib/chef/resource/batch.rb
@@ -1059,6 +1166,7 @@ files:
 - lib/chef/resource/http_request.rb
 - lib/chef/resource/ifconfig.rb
 - lib/chef/resource/ips_package.rb
+- lib/chef/resource/ksh.rb
 - lib/chef/resource/link.rb
 - lib/chef/resource/log.rb
 - lib/chef/resource/lwrp_base.rb
@@ -1098,6 +1206,7 @@ files:
 - lib/chef/resource/windows_script.rb
 - lib/chef/resource/windows_service.rb
 - lib/chef/resource/yum_package.rb
+- lib/chef/resource/zypper_package.rb
 - lib/chef/resource_builder.rb
 - lib/chef/resource_collection.rb
 - lib/chef/resource_collection/resource_collection_serialization.rb
@@ -1132,6 +1241,7 @@ files:
 - lib/chef/shell_out.rb
 - lib/chef/tasks/chef_repo.rake
 - lib/chef/user.rb
+- lib/chef/user_v1.rb
 - lib/chef/util/backup.rb
 - lib/chef/util/diff.rb
 - lib/chef/util/dsc/configuration_generator.rb
@@ -1167,17 +1277,21 @@ files:
 - lib/chef/win32/api/net.rb
 - lib/chef/win32/api/process.rb
 - lib/chef/win32/api/psapi.rb
+- lib/chef/win32/api/registry.rb
 - lib/chef/win32/api/security.rb
 - lib/chef/win32/api/synchronization.rb
 - lib/chef/win32/api/system.rb
 - lib/chef/win32/api/unicode.rb
 - lib/chef/win32/crypto.rb
 - lib/chef/win32/error.rb
+- lib/chef/win32/eventlog.rb
 - lib/chef/win32/file.rb
 - lib/chef/win32/file/info.rb
+- lib/chef/win32/file/version_info.rb
 - lib/chef/win32/handle.rb
 - lib/chef/win32/memory.rb
 - lib/chef/win32/mutex.rb
+- lib/chef/win32/net.rb
 - lib/chef/win32/process.rb
 - lib/chef/win32/registry.rb
 - lib/chef/win32/security.rb
@@ -1187,6 +1301,7 @@ files:
 - lib/chef/win32/security/security_descriptor.rb
 - lib/chef/win32/security/sid.rb
 - lib/chef/win32/security/token.rb
+- lib/chef/win32/system.rb
 - lib/chef/win32/unicode.rb
 - lib/chef/win32/version.rb
 - lib/chef/workstation_config_loader.rb
@@ -1204,6 +1319,25 @@ files:
 - spec/data/apt/chef-integration-test-1.1/debian/files
 - spec/data/apt/chef-integration-test-1.1/debian/rules
 - spec/data/apt/chef-integration-test-1.1/debian/source/format
+- spec/data/apt/chef-integration-test2-1.0/debian/changelog
+- spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log
+- spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars
+- spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles
+- spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control
+- spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums
+- spec/data/apt/chef-integration-test2-1.0/debian/compat
+- spec/data/apt/chef-integration-test2-1.0/debian/conffiles
+- spec/data/apt/chef-integration-test2-1.0/debian/control
+- spec/data/apt/chef-integration-test2-1.0/debian/copyright
+- spec/data/apt/chef-integration-test2-1.0/debian/files
+- spec/data/apt/chef-integration-test2-1.0/debian/rules
+- spec/data/apt/chef-integration-test2-1.0/debian/source/format
+- spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz
+- spec/data/apt/chef-integration-test2_1.0-1.dsc
+- spec/data/apt/chef-integration-test2_1.0-1_amd64.build
+- spec/data/apt/chef-integration-test2_1.0-1_amd64.changes
+- spec/data/apt/chef-integration-test2_1.0-1_amd64.deb
+- spec/data/apt/chef-integration-test2_1.0.orig.tar.gz
 - spec/data/apt/chef-integration-test_1.0-1_amd64.changes
 - spec/data/apt/chef-integration-test_1.0-1_amd64.deb
 - spec/data/apt/chef-integration-test_1.0.orig.tar.gz
@@ -1304,7 +1438,10 @@ files:
 - spec/data/cookbooks/openldap/recipes/return.rb
 - spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb
 - spec/data/cookbooks/openldap/templates/default/helper_test.erb
+- spec/data/cookbooks/openldap/templates/default/helpers.erb
 - spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb
+- spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb
+- spec/data/cookbooks/openldap/templates/default/nested_partial.erb
 - spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb
 - spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb
 - spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb
@@ -1316,6 +1453,7 @@ files:
 - spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed
 - spec/data/cookbooks/preseed/templates/default/preseed-template.seed
 - spec/data/definitions/test.rb
+- spec/data/dsc_lcm.pfx
 - spec/data/environment-config.rb
 - spec/data/file-providers-method-snapshot-chef-11-4.json
 - spec/data/fileedit/blank
@@ -1417,6 +1555,8 @@ files:
 - spec/data/run_context/cookbooks/dependency2/providers/provider.rb
 - spec/data/run_context/cookbooks/dependency2/recipes/default.rb
 - spec/data/run_context/cookbooks/dependency2/resources/resource.rb
+- spec/data/run_context/cookbooks/include/recipes/default.rb
+- spec/data/run_context/cookbooks/include/recipes/includee.rb
 - spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb
 - spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb
 - spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb
@@ -1486,6 +1626,7 @@ files:
 - spec/functional/knife/exec_spec.rb
 - spec/functional/knife/smoke_test.rb
 - spec/functional/knife/ssh_spec.rb
+- spec/functional/mixin/powershell_out_spec.rb
 - spec/functional/mixin/shell_out_spec.rb
 - spec/functional/notifications_spec.rb
 - spec/functional/provider/remote_file/cache_control_data_spec.rb
@@ -1501,6 +1642,7 @@ files:
 - spec/functional/resource/cron_spec.rb
 - spec/functional/resource/deploy_revision_spec.rb
 - spec/functional/resource/directory_spec.rb
+- spec/functional/resource/dpkg_package_spec.rb
 - spec/functional/resource/dsc_resource_spec.rb
 - spec/functional/resource/dsc_script_spec.rb
 - spec/functional/resource/env_spec.rb
@@ -1513,7 +1655,7 @@ files:
 - spec/functional/resource/mount_spec.rb
 - spec/functional/resource/ohai_spec.rb
 - spec/functional/resource/package_spec.rb
-- spec/functional/resource/powershell_spec.rb
+- spec/functional/resource/powershell_script_spec.rb
 - spec/functional/resource/reboot_spec.rb
 - spec/functional/resource/registry_spec.rb
 - spec/functional/resource/remote_directory_spec.rb
@@ -1522,6 +1664,8 @@ files:
 - spec/functional/resource/template_spec.rb
 - spec/functional/resource/user/dscl_spec.rb
 - spec/functional/resource/user/useradd_spec.rb
+- spec/functional/resource/user/windows_spec.rb
+- spec/functional/resource/windows_package_spec.rb
 - spec/functional/resource/windows_service_spec.rb
 - spec/functional/rest_spec.rb
 - spec/functional/run_lock_spec.rb
@@ -1531,9 +1675,11 @@ files:
 - spec/functional/util/powershell/cmdlet_spec.rb
 - spec/functional/version_spec.rb
 - spec/functional/win32/crypto_spec.rb
-- spec/functional/win32/registry_helper_spec.rb
+- spec/functional/win32/registry_spec.rb
 - spec/functional/win32/security_spec.rb
 - spec/functional/win32/service_manager_spec.rb
+- spec/functional/win32/sid_spec.rb
+- spec/functional/win32/version_info_spec.rb
 - spec/functional/win32/versions_spec.rb
 - spec/integration/client/client_spec.rb
 - spec/integration/client/ipv6_spec.rb
@@ -1554,6 +1700,13 @@ files:
 - spec/integration/knife/show_spec.rb
 - spec/integration/knife/upload_spec.rb
 - spec/integration/recipes/lwrp_inline_resources_spec.rb
+- spec/integration/recipes/lwrp_spec.rb
+- spec/integration/recipes/provider_choice.rb
+- spec/integration/recipes/recipe_dsl_spec.rb
+- spec/integration/recipes/remote_directory.rb
+- spec/integration/recipes/resource_action_spec.rb
+- spec/integration/recipes/resource_converge_if_changed_spec.rb
+- spec/integration/recipes/resource_load_spec.rb
 - spec/integration/solo/solo_spec.rb
 - spec/rcov.opts
 - spec/scripts/ssl-serve.rb
@@ -1562,10 +1715,13 @@ files:
 - spec/stress/win32/memory_spec.rb
 - spec/stress/win32/security_spec.rb
 - spec/support/chef_helpers.rb
+- spec/support/key_helpers.rb
 - spec/support/lib/chef/provider/easy.rb
+- spec/support/lib/chef/provider/openldap_includer.rb
 - spec/support/lib/chef/provider/snakeoil.rb
 - spec/support/lib/chef/resource/cat.rb
 - spec/support/lib/chef/resource/one_two_three_four.rb
+- spec/support/lib/chef/resource/openldap_includer.rb
 - spec/support/lib/chef/resource/with_state.rb
 - spec/support/lib/chef/resource/zen_follower.rb
 - spec/support/lib/chef/resource/zen_master.rb
@@ -1573,15 +1729,14 @@ files:
 - spec/support/matchers/leak.rb
 - spec/support/mock/constant.rb
 - spec/support/mock/platform.rb
-- spec/support/pedant/Gemfile
-- spec/support/pedant/pedant_config.rb
-- spec/support/pedant/run_pedant.rb
-- spec/support/pedant/stickywicket.pem
 - spec/support/platform_helpers.rb
 - spec/support/platforms/prof/gc.rb
 - spec/support/platforms/prof/win32.rb
 - spec/support/platforms/win32/spec_service.rb
+- spec/support/shared/context/client.rb
 - spec/support/shared/context/config.rb
+- spec/support/shared/context/win32.rb
+- spec/support/shared/examples/client.rb
 - spec/support/shared/functional/diff_disabled.rb
 - spec/support/shared/functional/directory_resource.rb
 - spec/support/shared/functional/file_resource.rb
@@ -1598,17 +1753,22 @@ files:
 - spec/support/shared/matchers/match_environment_variable.rb
 - spec/support/shared/shared_examples.rb
 - spec/support/shared/unit/api_error_inspector.rb
+- spec/support/shared/unit/api_versioning.rb
 - spec/support/shared/unit/execute_resource.rb
 - spec/support/shared/unit/file_system_support.rb
+- spec/support/shared/unit/knife_shared.rb
+- spec/support/shared/unit/mock_shellout.rb
 - spec/support/shared/unit/platform_introspector.rb
 - spec/support/shared/unit/provider/file.rb
 - spec/support/shared/unit/provider/useradd_based_user_provider.rb
 - spec/support/shared/unit/resource/static_provider_resolution.rb
 - spec/support/shared/unit/script_resource.rb
+- spec/support/shared/unit/user_and_client_shared.rb
 - spec/support/shared/unit/windows_script_resource.rb
 - spec/tiny_server.rb
 - spec/unit/api_client/registration_spec.rb
 - spec/unit/api_client_spec.rb
+- spec/unit/api_client_v1_spec.rb
 - spec/unit/application/agent_spec.rb
 - spec/unit/application/apply_spec.rb
 - spec/unit/application/client_spec.rb
@@ -1619,6 +1779,7 @@ files:
 - spec/unit/audit/audit_event_proxy_spec.rb
 - spec/unit/audit/audit_reporter_spec.rb
 - spec/unit/audit/control_group_data_spec.rb
+- spec/unit/audit/logger_spec.rb
 - spec/unit/audit/rspec_formatter_spec.rb
 - spec/unit/audit/runner_spec.rb
 - spec/unit/chef_class_spec.rb
@@ -1626,9 +1787,11 @@ files:
 - spec/unit/chef_fs/data_handler/group_handler_spec.rb
 - spec/unit/chef_fs/diff_spec.rb
 - spec/unit/chef_fs/file_pattern_spec.rb
+- spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb
 - spec/unit/chef_fs/file_system/operation_failed_error_spec.rb
 - spec/unit/chef_fs/file_system_spec.rb
 - spec/unit/chef_fs/parallelizer.rb
+- spec/unit/chef_fs/path_util_spec.rb
 - spec/unit/chef_spec.rb
 - spec/unit/client_spec.rb
 - spec/unit/config_fetcher_spec.rb
@@ -1657,9 +1820,12 @@ files:
 - spec/unit/dsl/reboot_pending_spec.rb
 - spec/unit/dsl/recipe_spec.rb
 - spec/unit/dsl/regsitry_helper_spec.rb
+- spec/unit/dsl/resources_spec.rb
 - spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb
 - spec/unit/encrypted_data_bag_item_spec.rb
 - spec/unit/environment_spec.rb
+- spec/unit/event_dispatch/dispatcher_spec.rb
+- spec/unit/event_dispatch/dsl_spec.rb
 - spec/unit/exceptions_spec.rb
 - spec/unit/file_access_control_spec.rb
 - spec/unit/file_cache_spec.rb
@@ -1667,6 +1833,8 @@ files:
 - spec/unit/file_content_management/deploy/mv_unix_spec.rb
 - spec/unit/file_content_management/deploy/mv_windows_spec.rb
 - spec/unit/formatters/base_spec.rb
+- spec/unit/formatters/doc_spec.rb
+- spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb
 - spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
 - spec/unit/formatters/error_inspectors/cookbook_resolve_error_inspector_spec.rb
 - spec/unit/formatters/error_inspectors/cookbook_sync_error_inspector_spec.rb
@@ -1678,6 +1846,7 @@ files:
 - spec/unit/guard_interpreter_spec.rb
 - spec/unit/handler/json_file_spec.rb
 - spec/unit/handler_spec.rb
+- spec/unit/http/authenticator_spec.rb
 - spec/unit/http/basic_client_spec.rb
 - spec/unit/http/http_request_spec.rb
 - spec/unit/http/json_input_spec.rb
@@ -1687,6 +1856,7 @@ files:
 - spec/unit/http/validate_content_length_spec.rb
 - spec/unit/http_spec.rb
 - spec/unit/json_compat_spec.rb
+- spec/unit/key_spec.rb
 - spec/unit/knife/bootstrap/chef_vault_handler_spec.rb
 - spec/unit/knife/bootstrap/client_builder_spec.rb
 - spec/unit/knife/bootstrap_spec.rb
@@ -1715,6 +1885,9 @@ files:
 - spec/unit/knife/cookbook_upload_spec.rb
 - spec/unit/knife/core/bootstrap_context_spec.rb
 - spec/unit/knife/core/cookbook_scm_repo_spec.rb
+- spec/unit/knife/core/custom_manifest_loader_spec.rb
+- spec/unit/knife/core/gem_glob_loader_spec.rb
+- spec/unit/knife/core/hashed_command_loader_spec.rb
 - spec/unit/knife/core/object_loader_spec.rb
 - spec/unit/knife/core/subcommand_loader_spec.rb
 - spec/unit/knife/core/ui_spec.rb
@@ -1731,6 +1904,12 @@ files:
 - spec/unit/knife/environment_list_spec.rb
 - spec/unit/knife/environment_show_spec.rb
 - spec/unit/knife/index_rebuild_spec.rb
+- spec/unit/knife/key_create_spec.rb
+- spec/unit/knife/key_delete_spec.rb
+- spec/unit/knife/key_edit_spec.rb
+- spec/unit/knife/key_helper.rb
+- spec/unit/knife/key_list_spec.rb
+- spec/unit/knife/key_show_spec.rb
 - spec/unit/knife/knife_help.rb
 - spec/unit/knife/node_bulk_delete_spec.rb
 - spec/unit/knife/node_delete_spec.rb
@@ -1742,6 +1921,12 @@ files:
 - spec/unit/knife/node_run_list_remove_spec.rb
 - spec/unit/knife/node_run_list_set_spec.rb
 - spec/unit/knife/node_show_spec.rb
+- spec/unit/knife/osc_user_create_spec.rb
+- spec/unit/knife/osc_user_delete_spec.rb
+- spec/unit/knife/osc_user_edit_spec.rb
+- spec/unit/knife/osc_user_list_spec.rb
+- spec/unit/knife/osc_user_reregister_spec.rb
+- spec/unit/knife/osc_user_show_spec.rb
 - spec/unit/knife/raw_spec.rb
 - spec/unit/knife/role_bulk_delete_spec.rb
 - spec/unit/knife/role_create_spec.rb
@@ -1774,9 +1959,12 @@ files:
 - spec/unit/knife/user_reregister_spec.rb
 - spec/unit/knife/user_show_spec.rb
 - spec/unit/knife_spec.rb
+- spec/unit/log/syslog_spec.rb
+- spec/unit/log/winevt_spec.rb
 - spec/unit/log_spec.rb
 - spec/unit/lwrp_spec.rb
 - spec/unit/mash_spec.rb
+- spec/unit/mixin/api_version_request_handling_spec.rb
 - spec/unit/mixin/checksum_spec.rb
 - spec/unit/mixin/command_spec.rb
 - spec/unit/mixin/convert_to_class_name_spec.rb
@@ -1786,10 +1974,16 @@ files:
 - spec/unit/mixin/homebrew_user_spec.rb
 - spec/unit/mixin/params_validate_spec.rb
 - spec/unit/mixin/path_sanity_spec.rb
+- spec/unit/mixin/powershell_out_spec.rb
 - spec/unit/mixin/powershell_type_coercions_spec.rb
+- spec/unit/mixin/properties_spec.rb
+- spec/unit/mixin/proxified_socket_spec.rb
 - spec/unit/mixin/securable_spec.rb
 - spec/unit/mixin/shell_out_spec.rb
+- spec/unit/mixin/subclass_directive_spec.rb
 - spec/unit/mixin/template_spec.rb
+- spec/unit/mixin/unformatter_spec.rb
+- spec/unit/mixin/uris_spec.rb
 - spec/unit/mixin/windows_architecture_helper_spec.rb
 - spec/unit/mixin/xml_escape_spec.rb
 - spec/unit/monkey_patches/uri_spec.rb
@@ -1801,9 +1995,13 @@ files:
 - spec/unit/org_spec.rb
 - spec/unit/platform/query_helpers_spec.rb
 - spec/unit/platform_spec.rb
+- spec/unit/policy_builder/dynamic_spec.rb
 - spec/unit/policy_builder/expand_node_object_spec.rb
 - spec/unit/policy_builder/policyfile_spec.rb
 - spec/unit/policy_builder_spec.rb
+- spec/unit/property/state_spec.rb
+- spec/unit/property/validation_spec.rb
+- spec/unit/property_spec.rb
 - spec/unit/provider/breakpoint_spec.rb
 - spec/unit/provider/cookbook_file/content_spec.rb
 - spec/unit/provider/cookbook_file_spec.rb
@@ -1862,13 +2060,13 @@ files:
 - spec/unit/provider/package/rubygems_spec.rb
 - spec/unit/provider/package/smartos_spec.rb
 - spec/unit/provider/package/solaris_spec.rb
+- spec/unit/provider/package/windows/exe_spec.rb
 - spec/unit/provider/package/windows/msi_spec.rb
 - spec/unit/provider/package/windows_spec.rb
 - spec/unit/provider/package/yum_spec.rb
 - spec/unit/provider/package/zypper_spec.rb
 - spec/unit/provider/package_spec.rb
-- spec/unit/provider/package_spec.rbe
-- spec/unit/provider/powershell_spec.rb
+- spec/unit/provider/powershell_script_spec.rb
 - spec/unit/provider/registry_key_spec.rb
 - spec/unit/provider/remote_directory_spec.rb
 - spec/unit/provider/remote_file/cache_control_data_spec.rb
@@ -1877,6 +2075,7 @@ files:
 - spec/unit/provider/remote_file/ftp_spec.rb
 - spec/unit/provider/remote_file/http_spec.rb
 - spec/unit/provider/remote_file/local_file_spec.rb
+- spec/unit/provider/remote_file/network_file_spec.rb
 - spec/unit/provider/remote_file_spec.rb
 - spec/unit/provider/route_spec.rb
 - spec/unit/provider/ruby_block_spec.rb
@@ -1913,7 +2112,6 @@ files:
 - spec/unit/provider_spec.rb
 - spec/unit/pure_application_spec.rb
 - spec/unit/recipe_spec.rb
-- spec/unit/registry_helper_spec.rb
 - spec/unit/resource/apt_package_spec.rb
 - spec/unit/resource/bash_spec.rb
 - spec/unit/resource/batch_spec.rb
@@ -1944,6 +2142,7 @@ files:
 - spec/unit/resource/http_request_spec.rb
 - spec/unit/resource/ifconfig_spec.rb
 - spec/unit/resource/ips_package_spec.rb
+- spec/unit/resource/ksh_spec.rb
 - spec/unit/resource/link_spec.rb
 - spec/unit/resource/log_spec.rb
 - spec/unit/resource/macports_package_spec.rb
@@ -1955,7 +2154,7 @@ files:
 - spec/unit/resource/pacman_package_spec.rb
 - spec/unit/resource/perl_spec.rb
 - spec/unit/resource/portage_package_spec.rb
-- spec/unit/resource/powershell_spec.rb
+- spec/unit/resource/powershell_script_spec.rb
 - spec/unit/resource/python_spec.rb
 - spec/unit/resource/registry_key_spec.rb
 - spec/unit/resource/remote_directory_spec.rb
@@ -1984,10 +2183,12 @@ files:
 - spec/unit/resource_collection_spec.rb
 - spec/unit/resource_definition_spec.rb
 - spec/unit/resource_reporter_spec.rb
+- spec/unit/resource_resolver_spec.rb
 - spec/unit/resource_spec.rb
 - spec/unit/rest/auth_credentials_spec.rb
 - spec/unit/rest_spec.rb
 - spec/unit/role_spec.rb
+- spec/unit/run_context/child_run_context_spec.rb
 - spec/unit/run_context/cookbook_compiler_spec.rb
 - spec/unit/run_context_spec.rb
 - spec/unit/run_list/run_list_expansion_spec.rb
@@ -2005,6 +2206,7 @@ files:
 - spec/unit/shell_out_spec.rb
 - spec/unit/shell_spec.rb
 - spec/unit/user_spec.rb
+- spec/unit/user_v1_spec.rb
 - spec/unit/util/backup_spec.rb
 - spec/unit/util/diff_spec.rb
 - spec/unit/util/dsc/configuration_generator_spec.rb
@@ -2013,7 +2215,6 @@ files:
 - spec/unit/util/dsc/resource_store.rb
 - spec/unit/util/editor_spec.rb
 - spec/unit/util/file_edit_spec.rb
-- spec/unit/util/path_helper_spec.rb
 - spec/unit/util/powershell/cmdlet_spec.rb
 - spec/unit/util/powershell/ps_credential_spec.rb
 - spec/unit/util/selinux_spec.rb
@@ -2022,10 +2223,12 @@ files:
 - spec/unit/version_class_spec.rb
 - spec/unit/version_constraint/platform_spec.rb
 - spec/unit/version_constraint_spec.rb
+- spec/unit/win32/registry_spec.rb
 - spec/unit/windows_service_spec.rb
-- spec/unit/workstation_config_loader_spec.rb
+- tasks/external_tests.rb
+- tasks/maintainers.rb
 - tasks/rspec.rb
-homepage: http://www.getchef.com
+homepage: http://www.chef.io
 licenses:
 - Apache-2.0
 metadata: {}
@@ -2045,7 +2248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
       version: '0'
 requirements: []
 rubyforge_project: 
-rubygems_version: 2.4.4
+rubygems_version: 2.4.5
 signing_key: 
 specification_version: 4
 summary: A systems integration framework, built to bring the benefits of configuration
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/changelog b/spec/data/apt/chef-integration-test2-1.0/debian/changelog
new file mode 100644
index 0000000..1b846f8
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/changelog
@@ -0,0 +1,5 @@
+chef-integration-test2 (1.0-1) unstable; urgency=low
+
+  * Initial release (Closes: #CHEF-1718)
+
+ -- Joshua Timberman <joshua at opscode.com>  Thu, 30 Sep 2010 09:53:45 -0600
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log
new file mode 100644
index 0000000..2d06fcd
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log
@@ -0,0 +1,45 @@
+dh_auto_configure
+dh_auto_build
+dh_auto_test
+dh_prep
+dh_installdirs
+dh_auto_install
+dh_install
+dh_installdocs
+dh_installchangelogs
+dh_installexamples
+dh_installman
+dh_installcatalogs
+dh_installcron
+dh_installdebconf
+dh_installemacsen
+dh_installifupdown
+dh_installinfo
+dh_pysupport
+dh_installinit
+dh_installmenu
+dh_installmime
+dh_installmodules
+dh_installlogcheck
+dh_installlogrotate
+dh_installpam
+dh_installppp
+dh_installudev
+dh_installwm
+dh_installxfonts
+dh_bugfiles
+dh_lintian
+dh_gconf
+dh_icons
+dh_perl
+dh_usrlocal
+dh_link
+dh_compress
+dh_fixperms
+dh_strip
+dh_makeshlibs
+dh_shlibdeps
+dh_installdeb
+dh_gencontrol
+dh_md5sums
+dh_builddeb
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars
new file mode 100644
index 0000000..abd3ebe
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars
@@ -0,0 +1 @@
+misc:Depends=
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles
new file mode 100644
index 0000000..ac4307e
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles
@@ -0,0 +1 @@
+/usr/share/doc/chef-integration-test2/copyright
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control
new file mode 100644
index 0000000..27d53d9
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control
@@ -0,0 +1,10 @@
+Package: chef-integration-test2
+Version: 1.0-1
+Architecture: amd64
+Maintainer: Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+Installed-Size: 36
+Section: ruby
+Priority: extra
+Homepage: http://tickets.opscode.com
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums
new file mode 100644
index 0000000..144b793
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums
@@ -0,0 +1 @@
+8b3b9ff6cdfe7d7b2b8b8d4f7b9e381f  usr/share/doc/chef-integration-test2/changelog.Debian.gz
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/compat b/spec/data/apt/chef-integration-test2-1.0/debian/compat
new file mode 100644
index 0000000..7f8f011
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/conffiles b/spec/data/apt/chef-integration-test2-1.0/debian/conffiles
new file mode 100644
index 0000000..ac4307e
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/conffiles
@@ -0,0 +1 @@
+/usr/share/doc/chef-integration-test2/copyright
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/control b/spec/data/apt/chef-integration-test2-1.0/debian/control
new file mode 100644
index 0000000..f2731a6
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/control
@@ -0,0 +1,13 @@
+Source: chef-integration-test2
+Section: ruby
+Priority: extra
+Maintainer: Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+Build-Depends: debhelper (>= 7.0.50~)
+Standards-Version: 3.8.4
+Homepage: http://tickets.opscode.com
+
+Package: chef-integration-test2
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/copyright b/spec/data/apt/chef-integration-test2-1.0/debian/copyright
new file mode 100644
index 0000000..72b6c65
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/copyright
@@ -0,0 +1,34 @@
+This work was packaged by:
+
+    Joshua Timberman <Joshua Timberman <joshua at opscode.com>> on Thu, 30 Sep 2010 09:53:45 -0600
+
+Upstream Author(s):
+
+    Opscode, Inc.
+
+Copyright:
+
+    Copyright (C) 2010 Opscode, Inc
+
+License:
+
+    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.
+
+The Debian packaging is:
+
+    Copyright (C) 2010 Opscode, Inc (<legal at opscode.com>)
+
+
+and is licensed under the Apache 2.0 license.
+
+See "/usr/share/common-licenses/Apache-2.0"
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/files b/spec/data/apt/chef-integration-test2-1.0/debian/files
new file mode 100644
index 0000000..640e4c6
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/files
@@ -0,0 +1 @@
+chef-integration-test2_1.0-1_amd64.deb ruby extra
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/rules b/spec/data/apt/chef-integration-test2-1.0/debian/rules
new file mode 100755
index 0000000..b760bee
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/rules
@@ -0,0 +1,13 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+	dh $@ 
diff --git a/spec/data/apt/chef-integration-test2-1.0/debian/source/format b/spec/data/apt/chef-integration-test2-1.0/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2-1.0/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz b/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz
new file mode 100644
index 0000000..6c002a7
Binary files /dev/null and b/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz differ
diff --git a/spec/data/apt/chef-integration-test2_1.0-1.dsc b/spec/data/apt/chef-integration-test2_1.0-1.dsc
new file mode 100644
index 0000000..b247f49
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2_1.0-1.dsc
@@ -0,0 +1,18 @@
+Format: 3.0 (quilt)
+Source: chef-integration-test2
+Binary: chef-integration-test2
+Architecture: any
+Version: 1.0-1
+Maintainer: Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+Homepage: http://tickets.opscode.com
+Standards-Version: 3.8.4
+Build-Depends: debhelper (>= 7.0.50~)
+Checksums-Sha1: 
+ 755c304197c6559128aed206ea70643fec2bb90d 248 chef-integration-test2_1.0.orig.tar.gz
+ 8b7df49a9e2c57b4460c2738852db1156a21a089 1369 chef-integration-test2_1.0-1.debian.tar.gz
+Checksums-Sha256: 
+ 8b206a7b3d422290bc8d82bd700cb89f1c6e3962b96be6a3955c7a0159f9031c 248 chef-integration-test2_1.0.orig.tar.gz
+ 77a7956e222c35afcddc4a5a8d338ca6e36dc1fbd720af255ce2412885f82702 1369 chef-integration-test2_1.0-1.debian.tar.gz
+Files: 
+ f1f7d7bbe63ad631d25d707f564a8d33 248 chef-integration-test2_1.0.orig.tar.gz
+ 4fab5c1cd9a7b47c4f319af776f48a1d 1369 chef-integration-test2_1.0-1.debian.tar.gz
diff --git a/spec/data/apt/chef-integration-test2_1.0-1_amd64.build b/spec/data/apt/chef-integration-test2_1.0-1_amd64.build
new file mode 100644
index 0000000..8ef31d3
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2_1.0-1_amd64.build
@@ -0,0 +1,91 @@
+ dpkg-buildpackage -rfakeroot -D -us -uc
+dpkg-buildpackage: warning: using a gain-root-command while being root
+dpkg-buildpackage: set CFLAGS to default value: -g -O2
+dpkg-buildpackage: set CPPFLAGS to default value: 
+dpkg-buildpackage: set LDFLAGS to default value: -Wl,-Bsymbolic-functions
+dpkg-buildpackage: set FFLAGS to default value: -g -O2
+dpkg-buildpackage: set CXXFLAGS to default value: -g -O2
+dpkg-buildpackage: source package chef-integration-test2
+dpkg-buildpackage: source version 1.0-1
+dpkg-buildpackage: source changed by Joshua Timberman <joshua at opscode.com>
+dpkg-buildpackage: host architecture amd64
+ fakeroot debian/rules clean
+dh clean 
+   dh_testdir
+   dh_auto_clean
+   dh_clean
+ dpkg-source -b chef-integration-test2-1.0
+dpkg-source: info: using source format `3.0 (quilt)'
+dpkg-source: info: building chef-integration-test2 using existing ./chef-integration-test2_1.0.orig.tar.gz
+dpkg-source: warning: ignoring deletion of directory cache
+dpkg-source: warning: ignoring deletion of directory cache/chef-integration-test2
+dpkg-source: warning: ignoring deletion of file cache/chef-integration-test2/contents
+dpkg-source: info: building chef-integration-test2 in chef-integration-test2_1.0-1.debian.tar.gz
+dpkg-source: info: building chef-integration-test2 in chef-integration-test2_1.0-1.dsc
+ debian/rules build
+dh build 
+   dh_testdir
+   dh_auto_configure
+   dh_auto_build
+   dh_auto_test
+ fakeroot debian/rules binary
+dh binary 
+   dh_testroot
+   dh_prep
+   dh_installdirs
+   dh_auto_install
+   dh_install
+   dh_installdocs
+   dh_installchangelogs
+   dh_installexamples
+   dh_installman
+   dh_installcatalogs
+   dh_installcron
+   dh_installdebconf
+   dh_installemacsen
+   dh_installifupdown
+   dh_installinfo
+   dh_pysupport
+   dh_installinit
+   dh_installmenu
+   dh_installmime
+   dh_installmodules
+   dh_installlogcheck
+   dh_installlogrotate
+   dh_installpam
+   dh_installppp
+   dh_installudev
+   dh_installwm
+   dh_installxfonts
+   dh_bugfiles
+   dh_lintian
+   dh_gconf
+   dh_icons
+   dh_perl
+   dh_usrlocal
+   dh_link
+   dh_compress
+   dh_fixperms
+   dh_strip
+   dh_makeshlibs
+   dh_shlibdeps
+   dh_installdeb
+   dh_gencontrol
+dpkg-gencontrol: warning: unknown substitution variable ${shlibs:Depends}
+   dh_md5sums
+   dh_builddeb
+dpkg-deb: building package `chef-integration-test2' in `../chef-integration-test2_1.0-1_amd64.deb'.
+ dpkg-genchanges  >../chef-integration-test2_1.0-1_amd64.changes
+dpkg-genchanges: including full source code in upload
+dpkg-buildpackage: full upload (original source is included)
+Now running lintian...
+warning: lintian's authors do not recommend running it with root privileges!
+E: chef-integration-test2 source: maintainer-address-malformed Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+W: chef-integration-test2 source: changelog-should-mention-nmu
+W: chef-integration-test2 source: source-nmu-has-incorrect-version-number 1.0-1
+W: chef-integration-test2: new-package-should-close-itp-bug
+W: chef-integration-test2: wrong-bug-number-in-closes l3:#CHEF
+E: chef-integration-test2: file-in-usr-marked-as-conffile /usr/share/doc/chef-integration-test2/copyright
+E: chef-integration-test2: maintainer-address-malformed Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+W: chef-integration-test2: empty-binary-package
+Finished running lintian.
diff --git a/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes b/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes
new file mode 100644
index 0000000..be3cd45
--- /dev/null
+++ b/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes
@@ -0,0 +1,31 @@
+Format: 1.8
+Date: Thu, 30 Sep 2010 09:53:45 -0600
+Source: chef-integration-test2
+Binary: chef-integration-test2
+Architecture: source amd64
+Version: 1.0-1
+Distribution: unstable
+Urgency: low
+Maintainer: Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+Changed-By: Joshua Timberman <joshua at opscode.com>
+Description: 
+ chef-integration-test2 - Chef integration tests for APT in Cucumber
+Changes: 
+ chef-integration-test2 (1.0-1) unstable; urgency=low
+ .
+   * Initial release (Closes: #CHEF-1718)
+Checksums-Sha1: 
+ 7e065fdf71f4d798312b318a29cec43b7bc1c8e1 885 chef-integration-test2_1.0-1.dsc
+ 755c304197c6559128aed206ea70643fec2bb90d 248 chef-integration-test2_1.0.orig.tar.gz
+ 8b7df49a9e2c57b4460c2738852db1156a21a089 1369 chef-integration-test2_1.0-1.debian.tar.gz
+ f3f89c051bce36d40ef1be12d231c44b2d5be05b 1694 chef-integration-test2_1.0-1_amd64.deb
+Checksums-Sha256: 
+ 80d314349e1d978f242d05482ca81c9361739047daa4adcecc9e5e622fdc6fb4 885 chef-integration-test2_1.0-1.dsc
+ 8b206a7b3d422290bc8d82bd700cb89f1c6e3962b96be6a3955c7a0159f9031c 248 chef-integration-test2_1.0.orig.tar.gz
+ 77a7956e222c35afcddc4a5a8d338ca6e36dc1fbd720af255ce2412885f82702 1369 chef-integration-test2_1.0-1.debian.tar.gz
+ 19a767db0a947a350fb1c8492699e8a808fbe1838d4a582001106cfbe520ad8f 1694 chef-integration-test2_1.0-1_amd64.deb
+Files: 
+ 9f927b32d95b5406c696b5b0b23177e8 885 ruby extra chef-integration-test2_1.0-1.dsc
+ f1f7d7bbe63ad631d25d707f564a8d33 248 ruby extra chef-integration-test2_1.0.orig.tar.gz
+ 4fab5c1cd9a7b47c4f319af776f48a1d 1369 ruby extra chef-integration-test2_1.0-1.debian.tar.gz
+ 9914e6152e278b6828bcade3b3f5580c 1694 ruby extra chef-integration-test2_1.0-1_amd64.deb
diff --git a/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb b/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb
new file mode 100644
index 0000000..7b9b69d
Binary files /dev/null and b/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb differ
diff --git a/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz b/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz
new file mode 100644
index 0000000..18f7aa1
Binary files /dev/null and b/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz differ
diff --git a/spec/data/cookbooks/openldap/templates/default/helpers.erb b/spec/data/cookbooks/openldap/templates/default/helpers.erb
new file mode 100644
index 0000000..b973a52
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/helpers.erb
@@ -0,0 +1,14 @@
+<%= @cookbook_name %>
+<%= @recipe_name %>
+<%= @recipe_line_string %>
+<%= @recipe_path %>
+<%= @recipe_line %>
+<%= @template_name %>
+<%= @template_path %>
+<%= cookbook_name %>
+<%= recipe_name %>
+<%= recipe_line_string %>
+<%= recipe_path %>
+<%= recipe_line %>
+<%= template_name %>
+<%= template_path %>
diff --git a/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb b/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb
new file mode 100644
index 0000000..2d356ec
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb
@@ -0,0 +1 @@
+before <%= render 'nested_partial.erb', :variables => { :hello => @test } %> after
diff --git a/spec/data/cookbooks/openldap/templates/default/nested_partial.erb b/spec/data/cookbooks/openldap/templates/default/nested_partial.erb
new file mode 100644
index 0000000..415646c
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/nested_partial.erb
@@ -0,0 +1 @@
+{<%= @hello %>}
diff --git a/spec/data/dsc_lcm.pfx b/spec/data/dsc_lcm.pfx
new file mode 100644
index 0000000..3912ed3
Binary files /dev/null and b/spec/data/dsc_lcm.pfx differ
diff --git a/spec/data/lwrp/providers/buck_passer.rb b/spec/data/lwrp/providers/buck_passer.rb
index 9792e2c..2bbca07 100644
--- a/spec/data/lwrp/providers/buck_passer.rb
+++ b/spec/data/lwrp/providers/buck_passer.rb
@@ -1,12 +1,28 @@
 provides :buck_passer
 
+def without_deprecation_warnings(&block)
+  old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+  Chef::Config[:treat_deprecation_warnings_as_errors] = false
+  begin
+    block.call
+  ensure
+    Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+  end
+end
+
 action :pass_buck do
   lwrp_foo :prepared_thumbs do
     action :prepare_thumbs
-    provider :lwrp_thumb_twiddler
+    # We know there will be a deprecation error here; head it off
+    without_deprecation_warnings do
+      provider :lwrp_thumb_twiddler
+    end
   end
   lwrp_foo :twiddled_thumbs do
     action :twiddle_thumbs
-    provider :lwrp_thumb_twiddler
+    # We know there will be a deprecation error here; head it off
+    without_deprecation_warnings do
+      provider :lwrp_thumb_twiddler
+    end
   end
 end
diff --git a/spec/data/lwrp/providers/buck_passer_2.rb b/spec/data/lwrp/providers/buck_passer_2.rb
index d34da3c..c3bab72 100644
--- a/spec/data/lwrp/providers/buck_passer_2.rb
+++ b/spec/data/lwrp/providers/buck_passer_2.rb
@@ -1,10 +1,26 @@
+def without_deprecation_warnings(&block)
+  old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+  Chef::Config[:treat_deprecation_warnings_as_errors] = false
+  begin
+    block.call
+  ensure
+    Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+  end
+end
+
 action :pass_buck do
   lwrp_bar :prepared_eyes do
     action :prepare_eyes
-    provider :lwrp_paint_drying_watcher
+    # We know there will be a deprecation error here; head it off
+    without_deprecation_warnings do
+      provider :lwrp_paint_drying_watcher
+    end
   end
   lwrp_bar :dried_paint_watched do
     action :watch_paint_dry
-    provider :lwrp_paint_drying_watcher
+    # We know there will be a deprecation error here; head it off
+    without_deprecation_warnings do
+      provider :lwrp_paint_drying_watcher
+    end
   end
 end
diff --git a/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb b/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
index f5841fb..77c1111 100644
--- a/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
+++ b/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
@@ -3,11 +3,23 @@
 # are passed properly (as demonstrated by the call to generate_new_name).
 attr_reader :enclosed_resource
 
+def without_deprecation_warnings(&block)
+  old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+  Chef::Config[:treat_deprecation_warnings_as_errors] = false
+  begin
+    block.call
+  ensure
+    Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+  end
+end
+
 action :twiddle_thumbs do
   @enclosed_resource = lwrp_foo :foo do
     monkey generate_new_name(new_resource.monkey){ 'the monkey' }
-    action :twiddle_thumbs
-    provider :lwrp_monkey_name_printer
+    # We know there will be a deprecation error here; head it off
+    without_deprecation_warnings do
+      provider :lwrp_monkey_name_printer
+    end
   end
 end
 
diff --git a/spec/data/lwrp_override/resources/foo.rb b/spec/data/lwrp_override/resources/foo.rb
index 14decb9..2fc13d3 100644
--- a/spec/data/lwrp_override/resources/foo.rb
+++ b/spec/data/lwrp_override/resources/foo.rb
@@ -3,3 +3,8 @@
 actions :never_execute
 
 attribute :ever, :kind_of => String
+
+class ::Chef
+  def method_created_by_override_lwrp_foo
+  end
+end
diff --git a/spec/data/run_context/cookbooks/include/recipes/default.rb b/spec/data/run_context/cookbooks/include/recipes/default.rb
new file mode 100644
index 0000000..8d22994
--- /dev/null
+++ b/spec/data/run_context/cookbooks/include/recipes/default.rb
@@ -0,0 +1,24 @@
+module ::RanResources
+  def self.resources
+    @resources ||= []
+  end
+end
+class RunContextCustomResource < Chef::Resource
+  action :create do
+    ruby_block '4' do
+      block { RanResources.resources << 4 }
+    end
+    recipe_eval do
+      ruby_block '1' do
+        block { RanResources.resources << 1 }
+      end
+      include_recipe 'include::includee'
+      ruby_block '3' do
+        block { RanResources.resources << 3 }
+      end
+    end
+    ruby_block '5' do
+      block { RanResources.resources << 5 }
+    end
+  end
+end
diff --git a/spec/data/run_context/cookbooks/include/recipes/includee.rb b/spec/data/run_context/cookbooks/include/recipes/includee.rb
new file mode 100644
index 0000000..87bb7f1
--- /dev/null
+++ b/spec/data/run_context/cookbooks/include/recipes/includee.rb
@@ -0,0 +1,3 @@
+ruby_block '2' do
+  block { RanResources.resources << 2 }
+end
diff --git a/spec/data/trusted_certs/opscode.pem b/spec/data/trusted_certs/opscode.pem
index 37a3dd1..e421a4e 100644
--- a/spec/data/trusted_certs/opscode.pem
+++ b/spec/data/trusted_certs/opscode.pem
@@ -1,60 +1,57 @@
 -----BEGIN CERTIFICATE-----
-MIIFrDCCBJSgAwIBAgIQB1O/fCb6cEytJ4BP3HTbCTANBgkqhkiG9w0BAQUFADBI
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSIwIAYDVQQDExlE
-aWdpQ2VydCBTZWN1cmUgU2VydmVyIENBMB4XDTE0MDYxMDAwMDAwMFoXDTE1MDcw
-MTEyMDAwMFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
-BgNVBAcTB1NlYXR0bGUxGzAZBgNVBAoTEkNoZWYgU29mdHdhcmUsIEluYzEWMBQG
-A1UEAwwNKi5vcHNjb2RlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
-ggEBAMm+rf2RcPGBlZoM+hI4BxlaHbdRg1GZJ/T46UWFOBnZFVP++TX/pyjDsvns
-xymcQywtoN/26+UIys6oWX1um9ikEokvf67LdsUeemQGFHFky8X1Ka2hVtKnxBhi
-XZfvyHDR4IyFWU9AwmhnqySzxqCtynUu8Gktx7JVfqbRFMZ186pDcSw8LoaqjTVG
-SzO7eNH2sM3doMueAHj7ITc2wUzmfa0Pdh+K8UoCn/HopU5LzycziJVPYvUkLT2m
-YCV7VWRc+kObZseHhZAbyaDk3RgPQ/eRMhytAgbruBHWDqNesNw+ZA70w856Oj2Y
-geO7JF+5V6WvkywrF8vydaoM2l8CAwEAAaOCAm8wggJrMB8GA1UdIwQYMBaAFJBx
-2zfrc8jv3NUeErY0uitaoKaSMB0GA1UdDgQWBBQK5zjZwbcmcMNLnI2h1ioAldEV
-ujCBygYDVR0RBIHCMIG/gg0qLm9wc2NvZGUuY29tghBjb3JwLm9wc2NvZGUuY29t
-ghIqLmNvcnAub3BzY29kZS5jb22CDyoubGVhcm5jaGVmLmNvbYISKi5jb3JwLmdl
-dGNoZWYuY29tgg0qLmdldGNoZWYuY29tggwqLm9wc2NvZGUudXOCC2dldGNoZWYu
-Y29tggtvcHNjb2RlLmNvbYIRYXBpLmJlcmtzaGVsZi5jb22CDWxlYXJuY2hlZi5j
-b22CCm9wc2NvZGUudXMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUF
-BwMBBggrBgEFBQcDAjBhBgNVHR8EWjBYMCqgKKAmhiRodHRwOi8vY3JsMy5kaWdp
-Y2VydC5jb20vc3NjYS1nNi5jcmwwKqAooCaGJGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0
-LmNvbS9zc2NhLWc2LmNybDBCBgNVHSAEOzA5MDcGCWCGSAGG/WwBATAqMCgGCCsG
-AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMHgGCCsGAQUFBwEB
-BGwwajAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEIGCCsG
-AQUFBzAChjZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTZWN1
-cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQUFAAOCAQEA
-kgBpJ2t+St7SmWfeNU9EWAhy0NuUnRIi1jnqXdapfPmS6V/M0i2wP/p+crMty78e
-+3ieuF5s0GJBLs85Hikcl3SlrrbIBJxozov1TY6zeOi6+TCsdXer6t6iQKz36zno
-+k+T6lnMCyo9+pk1PhcAWyfo1Fz4xVOBVec/71VovFkkGD2//KB+sbDs+yh21N9M
-ReO7duj16rQSctfO9R2h65djBNlgz6hXY2nlw8/x3uFfZobXOxDrTcH6Z8HIslkE
-MiTXGix6zdqJaFRCWi+prnAztWs+jEy+v95VSEHPj3xpwZ9WjsxQN0kFA2EX61v/
-kGunmyhehGjblQRt7bpyiA==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEjzCCA3egAwIBAgIQBp4dt3/PHfupevXlyaJANzANBgkqhkiG9w0BAQUFADBh
+MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh
 MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
-QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEgxCzAJBgNVBAYTAlVT
-MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxIjAgBgNVBAMTGURpZ2lDZXJ0IFNlY3Vy
-ZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7V+Qh
-qdWbYDd+jqFhf4HiGsJ1ZNmRUAvkNkQkbjDSm3on+sJqrmpwCTi5IArIZRBKiKwx
-8tyS8mOhXYBjWYCSIxzm73ZKUDXJ2HE4ue3w5kKu0zgmeTD5IpTG26Y/QXiQ2N5c
-fml9+JAVOtChoL76srIZodgr0c6/a91Jq6OS/rWryME+7gEA2KlEuEJziMNh9atK
-gygK0tRJ+mqxzd9XLJTl4sqDX7e6YlwvaKXwwLn9K9HpH9gaYhW9/z2m98vv5ttl
-LyU47PvmIGZYljQZ0hXOIdMkzNkUb9j+Vcfnb7YPGoxJvinyulqagSY3JG/XSBJs
-Lln1nBi72fZo4t9FAgMBAAGjggFaMIIBVjASBgNVHRMBAf8ECDAGAQH/AgEAMA4G
-A1UdDwEB/wQEAwIBhjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6
-Ly9vY3NwLmRpZ2ljZXJ0LmNvbTB7BgNVHR8EdDByMDegNaAzhjFodHRwOi8vY3Js
-My5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMDegNaAzhjFo
-dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3Js
-MD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5k
-aWdpY2VydC5jb20vQ1BTMB0GA1UdDgQWBBSQcds363PI79zVHhK2NLorWqCmkjAf
-BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTANBgkqhkiG9w0BAQUFAAOC
-AQEAMM7RlVEArgYLoQ4CwBestn+PIPZAdXQczHixpE/q9NDEnaLegQcmH0CIUfAf
-z7dMQJnQ9DxxmHOIlywZ126Ej6QfnFog41FcsMWemWpPyGn3EP9OrRnZyVizM64M
-2ZYpnnGycGOjtpkWQh1l8/egHn3F1GUUsmKE1GxcCAzYbJMrtHZZitF//wPYwl24
-LyLWOPD2nGt9RuuZdPfrSg6ppgTre87wXGuYMVqYQOtpxAX0IKjKCDplbDgV9Vws
-slXkLGtB8L5cRspKKaBIXiDSRf8F3jSvcEuBOeLKB1d8tjHcISnivpcOd5AUUUDh
-v+PMGxmcJcqnBrJT3yOyzxIZow==
+QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg
+U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83
+nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd
+KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f
+/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX
+kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0
+/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C
+AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY
+aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6
+Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1
+oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD
+QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
+d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh
+xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB
+CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl
+5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA
+8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC
+2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit
+c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0
+j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFDTCCA/WgAwIBAgIQBZ8R1sZP2Lbc8x554UUQ2DANBgkqhkiG9w0BAQsFADBN
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E
+aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTQxMTEwMDAwMDAwWhcN
+MTcxMTE0MTIwMDAwWjBlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
+bjEQMA4GA1UEBxMHU2VhdHRsZTEbMBkGA1UEChMSQ2hlZiBTb2Z0d2FyZSwgSW5j
+MRIwEAYDVQQDDAkqLmNoZWYuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC3xCIczkV10O5jTDpbd4YlPLC6kfnVoOkno2N/OOlcLQu3ulj/Lj1j4r6e
+2XthJLcFgTO+y+1/IKnnpLKDfkx1YngWEBXEBP+MrrpDUKKs053s45/bI9QBPISA
+tXgnYxMH9Glo6FWWd13TUq++OKGw1p1wazH64XK4MAf5y/lkmWXIWumNuO35ZqtB
+ME3wJISwVHzHB2CQjlDklt+Mb0APEiIFIZflgu9JNBYzLdvUtxiz15FUZQI7SsYL
+TfXOD1KBNMWqN8snG2e5gRAzB2D161DFvAZt8OiYUe+3QurNlTYVzeHv1ok6UqgM
+ZcLzg8m801rRip0D7FCGvMCU/ktdAgMBAAGjggHPMIIByzAfBgNVHSMEGDAWgBQP
+gGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQUwldjw4Pb4HV+wxGZ7MSSRh+d
+pm4wHQYDVR0RBBYwFIIJKi5jaGVmLmlvggdjaGVmLmlvMA4GA1UdDwEB/wQEAwIF
+oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYDVR0fBGQwYjAvoC2g
+K4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nMy5jcmwwL6At
+oCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNoYTItZzMuY3JsMEIG
+A1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3
+LmRpZ2ljZXJ0LmNvbS9DUFMwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhho
+dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNl
+cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQw
+DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAvcTWenNuvvrhX2omm8LQ
+zWOuu8jqpoflACwD4lOSZ4TgOe4pQGCjXq8aRBD5k+goqQrPVf9lHnelUHFQac0Q
+5WT4YUmisUbF0S4uY5OGQymM52MvUWG4ODL4gaWhFvN+HAXrDPP/9iitsjV0QOnl
+CDq7Q4/XYRYW3opu5nLLbfW6v4QvF5yzZagEACGs7Vt32p6l391UcU8f6wiB3uMD
+eioCvjpv/+2YOUNlDPCM3uBubjUhHOwO817wBxXkzdk1OSRe4jzcw/uX6wL7birt
+fbaSkpilvVX529pSzB2Lvi9xWOoGMM578dpQ0h3PwhmmvKhhCWP+pI05k3oSkYCP
+ng==
 -----END CERTIFICATE-----
diff --git a/spec/functional/application_spec.rb b/spec/functional/application_spec.rb
index 00ff0f7..fe656dc 100644
--- a/spec/functional/application_spec.rb
+++ b/spec/functional/application_spec.rb
@@ -42,7 +42,7 @@ describe Chef::Application do
       Chef::Config[:ftp_proxy] = nil
       Chef::Config[:no_proxy] = nil
 
-      @app.configure_proxy_environment_variables
+      Chef::Config.export_proxies
     end
 
     it "saves built proxy to ENV which shell_out can use" do
diff --git a/spec/functional/audit/runner_spec.rb b/spec/functional/audit/runner_spec.rb
index 4949428..4f7f271 100644
--- a/spec/functional/audit/runner_spec.rb
+++ b/spec/functional/audit/runner_spec.rb
@@ -46,21 +46,15 @@ describe Chef::Audit::Runner do
     RSpec::Core::Sandbox.sandboxed { ex.run }
   end
 
-  before do
-    Chef::Config[:log_location] = stdout
-  end
-  
   describe "#run" do
 
     let(:audits) { {} }
     let(:run_context) { instance_double(Chef::RunContext, :events => events, :audits => audits) }
     let(:control_group_name) { "control_group_name" }
 
-    it "Correctly runs an empty controls block" do
-      in_sub_process do
-        runner.run
-      end
-    end
+    # Set cookbook path to include our parent, so that it will recognize this
+    # rspec file as one that should show up in the backtrace.
+    before(:each) { Chef::Config[:cookbook_path] = File.dirname(__FILE__) }
 
     shared_context "passing audit" do
       let(:audits) do
@@ -84,50 +78,40 @@ describe Chef::Audit::Runner do
       end
     end
 
-    context "there is a single successful control" do
-      include_context "passing audit"
-      it "correctly runs" do
-        in_sub_process do
-          runner.run
-
-          expect(stdout.string).to match(/1 example, 0 failures/)
+    describe "log location is stdout" do
+      before do
+        allow(Chef::Log).to receive(:info) do |msg|
+          stdout.puts(msg)
         end
       end
-    end
 
-    context "there is a single failing control" do
-      include_context "failing audit"
-      it "correctly runs" do
+      it "Correctly runs an empty controls block" do
         in_sub_process do
           runner.run
-
-          expect(stdout.string).to match(/Failure\/Error: expect\(2 - 1\)\.to eq\(0\)/)
-          expect(stdout.string).to match(/1 example, 1 failure/)
-          expect(stdout.string).to match(/# control_group_name should fail/)
         end
       end
-    end
 
-    describe "log location is a file" do
-      let(:tmpfile) { Tempfile.new("audit") }
-      before do
-        Chef::Config[:log_location] = tmpfile.path
-      end
+      context "there is a single successful control" do
+        include_context "passing audit"
+        it "correctly runs" do
+          in_sub_process do
+            runner.run
 
-      after do
-        tmpfile.close
-        tmpfile.unlink
+            expect(stdout.string).to match(/1 example, 0 failures/)
+          end
+        end
       end
 
-      include_context "failing audit"
-      it "correctly runs" do
-        in_sub_process do
-          runner.run
+      context "there is a single failing control" do
+        include_context "failing audit"
+        it "correctly runs" do
+          in_sub_process do
+            runner.run
 
-          contents = tmpfile.read
-          expect(contents).to match(/Failure\/Error: expect\(2 - 1\)\.to eq\(0\)/)
-          expect(contents).to match(/1 example, 1 failure/)
-          expect(contents).to match(/# control_group_name should fail/)
+            expect(stdout.string).to match(/Failure\/Error: expect\(2 - 1\)\.to eq\(0\)/)
+            expect(stdout.string).to match(/1 example, 1 failure/)
+            expect(stdout.string).to match(/# control_group_name should fail/)
+          end
         end
       end
     end
diff --git a/spec/functional/dsl/reboot_pending_spec.rb b/spec/functional/dsl/reboot_pending_spec.rb
index 14dd941..1d11f38 100644
--- a/spec/functional/dsl/reboot_pending_spec.rb
+++ b/spec/functional/dsl/reboot_pending_spec.rb
@@ -30,13 +30,6 @@ describe Chef::DSL::RebootPending, :windows_only do
     ohai
   end
 
-  def registry_unsafe?
-    registry.value_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }) ||
-    registry.key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired')
-    registry.key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') ||
-    registry.key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile')
-  end
-
   let(:node) { Chef::Node.new }
   let(:events) { Chef::EventDispatch::Dispatcher.new }
   let!(:ohai) { run_ohai } # Ensure we have necessary node data
@@ -45,76 +38,73 @@ describe Chef::DSL::RebootPending, :windows_only do
   let(:registry) { Chef::Win32::Registry.new(run_context) }
 
   describe "reboot_pending?" do
+    let(:reg_key) { nil }
+    let(:original_set) { false }
 
-    describe "when there is nothing to indicate a reboot is pending" do
-      it "should return false" do
-        skip "Found existing registry keys" if registry_unsafe?
-        expect(recipe.reboot_pending?).to be_falsey
-      end
-    end
+    before(:all) { @any_flag = Hash.new }
+
+    after { @any_flag[reg_key] = original_set }
 
     describe 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations' do
+      let(:reg_key) { 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager' }
+      let(:original_set) { registry.value_exists?(reg_key, { :name => 'PendingFileRenameOperations' }) }
+
       it "returns true if the registry value exists" do
-        skip "Found existing registry keys" if registry_unsafe?
-        registry.set_value('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager',
+        skip 'found existing registry key' if original_set
+        registry.set_value(reg_key,
             { :name => 'PendingFileRenameOperations', :type => :multi_string, :data => ['\??\C:\foo.txt|\??\C:\bar.txt'] })
 
         expect(recipe.reboot_pending?).to be_truthy
       end
 
       after do
-        unless registry_unsafe?
-          registry.delete_value('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' })
+        unless original_set
+          registry.delete_value(reg_key, { :name => 'PendingFileRenameOperations' })
         end
       end
     end
 
-    describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' do
+    describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' do
+      let(:reg_key) { 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' }
+      let(:original_set) { registry.key_exists?(reg_key) }
+
       it "returns true if the registry key exists" do
-        skip "Found existing registry keys" if registry_unsafe?
-        registry.create_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired', false)
+        skip 'found existing registry key' if original_set
+        pending "Permissions are limited to 'TrustedInstaller' by default"
+        registry.create_key(reg_key, false)
 
         expect(recipe.reboot_pending?).to be_truthy
       end
 
       after do
-        unless registry_unsafe?
-          registry.delete_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired', false)
+        unless original_set
+          registry.delete_key(reg_key, false)
         end
       end
     end
 
-    describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' do
+    describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' do
+      let(:reg_key) { 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' }
+      let(:original_set) { registry.key_exists?(reg_key) }
+
       it "returns true if the registry key exists" do
-        pending "Permissions are limited to 'TrustedInstaller' by default"
-        skip "Found existing registry keys" if registry_unsafe?
-        registry.create_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired', false)
+        skip 'found existing registry key' if original_set
+        registry.create_key(reg_key, false)
 
         expect(recipe.reboot_pending?).to be_truthy
       end
 
       after do
-        unless registry_unsafe?
-          registry.delete_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired', false)
+        unless original_set
+          registry.delete_key(reg_key, false)
         end
       end
     end
 
-    describe 'HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile\Flags' do
-      it "returns true if the registry key exists" do
-        skip "Found existing registry keys" if registry_unsafe?
-        registry.create_key('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', true)
-        registry.set_value('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile',
-                    { :name => 'Flags', :type => :dword, :data => 3 })
-
-        expect(recipe.reboot_pending?).to be_truthy
-      end
-
-      after do
-        unless registry_unsafe?
-          registry.delete_value('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', { :name => 'Flags' })
-          registry.delete_key('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', false)
-        end
+    describe "when there is nothing to indicate a reboot is pending" do
+      it "should return false" do
+        skip 'reboot pending' if @any_flag.any? { |_,v| v == true }
+        expect(recipe.reboot_pending?).to be_falsey
       end
     end
   end
diff --git a/spec/functional/knife/cookbook_delete_spec.rb b/spec/functional/knife/cookbook_delete_spec.rb
index 15ac8f5..bffad8c 100644
--- a/spec/functional/knife/cookbook_delete_spec.rb
+++ b/spec/functional/knife/cookbook_delete_spec.rb
@@ -40,20 +40,30 @@ describe Chef::Knife::CookbookDelete do
   end
 
   context "when the cookbook doesn't exist" do
-    before do
-      @log_output = StringIO.new
-
-      Chef::Log.logger = Logger.new(@log_output)
-      Chef::Log.level = :debug
+    let(:log_output) { StringIO.new }
 
+    before do
       @knife.name_args = %w{no-such-cookbook}
       @api.get("/cookbooks/no-such-cookbook", 404, Chef::JSONCompat.to_json({'error'=>'dear Tim, no. -Sent from my iPad'}))
     end
 
+    around do |ex|
+      old_logger = Chef::Log.logger
+      old_level = Chef::Log.level
+      begin
+        Chef::Log.logger = Logger.new(log_output)
+        Chef::Log.level = :debug
+        ex.run
+      ensure
+        Chef::Log.logger = old_logger
+        Chef::Log.level = old_level
+      end
+    end
+
     it "logs an error and exits" do
-      allow(@knife.ui).to receive(:stderr).and_return(@log_output)
+      allow(@knife.ui).to receive(:stderr).and_return(log_output)
       expect {@knife.run}.to raise_error(SystemExit)
-      expect(@log_output.string).to match(/Cannot find a cookbook named no-such-cookbook to delete/)
+      expect(log_output.string).to match(/Cannot find a cookbook named no-such-cookbook to delete/)
     end
 
   end
diff --git a/spec/functional/knife/ssh_spec.rb b/spec/functional/knife/ssh_spec.rb
index 5b8ad6f..0844a0a 100644
--- a/spec/functional/knife/ssh_spec.rb
+++ b/spec/functional/knife/ssh_spec.rb
@@ -31,6 +31,22 @@ describe Chef::Knife::Ssh do
     @server.stop
   end
 
+  let(:ssh_config) { Hash.new }
+  before do
+    allow(Net::SSH).to receive(:configuration_for).and_return(ssh_config)
+  end
+
+  # Force log level to info.
+  around do |ex|
+    old_level = Chef::Log.level
+    begin
+      Chef::Log.level = :info
+      ex.run
+    ensure
+      Chef::Log.level = old_level
+    end
+  end
+
   describe "identity file" do
     context "when knife[:ssh_identity_file] is set" do
       before do
@@ -40,7 +56,7 @@ describe Chef::Knife::Ssh do
 
       it "uses the ssh_identity_file" do
         @knife.run
-        expect(@knife.config[:identity_file]).to eq("~/.ssh/aws.rsa")
+        expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
       end
     end
 
@@ -52,7 +68,7 @@ describe Chef::Knife::Ssh do
 
       it "uses the ssh_identity_file" do
         @knife.run
-        expect(@knife.config[:identity_file]).to eq("~/.ssh/aws.rsa")
+        expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
       end
     end
 
@@ -64,13 +80,13 @@ describe Chef::Knife::Ssh do
 
       it "should use the value on the command line" do
         @knife.run
-        expect(@knife.config[:identity_file]).to eq("~/.ssh/aws.rsa")
+        expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
       end
 
       it "should override what is set in knife.rb" do
         Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/other.rsa"
         @knife.run
-        expect(@knife.config[:identity_file]).to eq("~/.ssh/aws.rsa")
+        expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
       end
     end
 
@@ -82,7 +98,7 @@ describe Chef::Knife::Ssh do
 
       it "uses the default" do
         @knife.run
-        expect(@knife.config[:identity_file]).to eq(nil)
+        expect(@knife.config[:ssh_identity_file]).to eq(nil)
       end
     end
   end
@@ -165,7 +181,7 @@ describe Chef::Knife::Ssh do
 
       it "uses the ssh_attribute" do
         @knife.run
-        expect(@knife.config[:attribute]).to eq("ec2.public_hostname")
+        expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("ec2.public_hostname")
       end
     end
 
@@ -177,7 +193,7 @@ describe Chef::Knife::Ssh do
 
       it "uses the default" do
         @knife.run
-        expect(@knife.config[:attribute]).to eq("fqdn")
+        expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("fqdn")
       end
     end
 
diff --git a/spec/functional/mixin/powershell_out_spec.rb b/spec/functional/mixin/powershell_out_spec.rb
new file mode 100644
index 0000000..9cc8aee
--- /dev/null
+++ b/spec/functional/mixin/powershell_out_spec.rb
@@ -0,0 +1,43 @@
+#
+# Copyright:: Copyright (c) 2014 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 'spec_helper'
+require 'chef/mixin/powershell_out'
+
+describe Chef::Mixin::PowershellOut, windows_only: true do
+  include Chef::Mixin::PowershellOut
+
+  describe "#powershell_out" do
+    it "runs a powershell command and collects stdout" do
+      expect(powershell_out("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+ProcessName/
+    end
+
+    it "does not raise exceptions when the command is invalid" do
+      powershell_out("this-is-not-a-valid-command").run_command
+    end
+  end
+
+  describe "#powershell_out!" do
+    it "runs a powershell command and collects stdout" do
+      expect(powershell_out!("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+ProcessName/
+    end
+
+    it "raises exceptions when the command is invalid" do
+      expect { powershell_out!("this-is-not-a-valid-command").run_command }.to raise_exception(Mixlib::ShellOut::ShellCommandFailed)
+    end
+  end
+end
diff --git a/spec/functional/notifications_spec.rb b/spec/functional/notifications_spec.rb
index a02fdff..f14f35e 100644
--- a/spec/functional/notifications_spec.rb
+++ b/spec/functional/notifications_spec.rb
@@ -74,6 +74,76 @@ describe "Notifications" do
     runner.converge
   end
 
+  it "should notify from one resource to another before" do
+    log_resource = recipe.declare_resource(:log, "log") do
+      message "This is a log message"
+      action :write
+      notifies :install, "package[vim]", :before
+    end
+    update_action(log_resource, 2)
+
+    package_resource = recipe.declare_resource(:package, "vim") do
+      action :nothing
+    end
+
+    actions = []
+    [ log_resource, package_resource ].each do |resource|
+      allow(resource).to receive(:run_action).and_wrap_original do |m, action, notification_type, notifying_resource|
+        actions << { resource: resource.to_s, action: action }
+        actions[-1][:why_run] = Chef::Config[:why_run] if Chef::Config[:why_run]
+        actions[-1][:notification_type] = notification_type if notification_type
+        actions[-1][:notifying_resource] = notifying_resource.to_s if notifying_resource
+        m.call(action, notification_type, notifying_resource)
+      end
+    end
+
+    runner.converge
+
+    expect(actions).to eq [
+      # First it runs why-run to check if the resource would update
+      { resource: log_resource.to_s,     action: :write,   why_run: true },
+      # Then it runs the before action
+      { resource: package_resource.to_s, action: :install, notification_type: :before, notifying_resource: log_resource.to_s },
+      # Then it runs the actual action
+      { resource: log_resource.to_s,     action: :write },
+      { resource: package_resource.to_s, action: :nothing }
+    ]
+  end
+
+  it "should not notify from one resource to another before if the resource is not updated" do
+    log_resource = recipe.declare_resource(:log, "log") do
+      message "This is a log message"
+      action :write
+      notifies :install, "package[vim]", :before
+    end
+
+    package_resource = recipe.declare_resource(:package, "vim") do
+      action :nothing
+    end
+
+    actions = []
+    [ log_resource, package_resource ].each do |resource|
+      allow(resource).to receive(:run_action).and_wrap_original do |m, action, notification_type, notifying_resource|
+        actions << { resource: resource.to_s, action: action }
+        actions[-1][:why_run] = Chef::Config[:why_run] if Chef::Config[:why_run]
+        actions[-1][:notification_type] = notification_type if notification_type
+        actions[-1][:notifying_resource] = notifying_resource.to_s if notifying_resource
+        m.call(action, notification_type, notifying_resource)
+      end
+    end
+
+    runner.converge
+
+    expect(actions).to eq [
+      # First it runs why-run to check if the resource would update
+      { resource: log_resource.to_s,     action: :write,   why_run: true },
+      # Then it does NOT run the before action
+      # Then it runs the actual action
+      { resource: log_resource.to_s,     action: :write },
+      { resource: package_resource.to_s, action: :nothing }
+    ]
+  end
+
   it "should notify from one resource to another delayed" do
     log_resource = recipe.declare_resource(:log, "log") do
       message "This is a log message"
@@ -94,7 +164,7 @@ describe "Notifications" do
 
     runner.converge
   end
-  
+
   describe "when one resource is defined lazily" do
 
     it "subscribes to a resource defined in a ruby block" do
@@ -158,10 +228,10 @@ describe "Notifications" do
   end
 
   # Mocks having the provider run successfully and update the resource
-  def update_action(resource)
+  def update_action(resource, times=1)
     p = Chef::Provider.new(resource, run_context)
-    expect(resource).to receive(:provider_for_action).and_return(p)
-    expect(p).to receive(:run_action) {
+    expect(resource).to receive(:provider_for_action).exactly(times).times.and_return(p)
+    expect(p).to receive(:run_action).exactly(times).times {
       resource.updated_by_last_action(true)
     }
   end
diff --git a/spec/functional/provider/whyrun_safe_ruby_block_spec.rb b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb
index b3c2333..2b582fe 100644
--- a/spec/functional/provider/whyrun_safe_ruby_block_spec.rb
+++ b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb
@@ -43,7 +43,7 @@ describe Chef::Resource::WhyrunSafeRubyBlock do
     end
 
     it "updates the evil laugh, even in why-run mode" do
-      new_resource.run_action(new_resource.action)
+      Array(new_resource.action).each {|action| new_resource.run_action(action) }
       expect($evil_global_evil_laugh).to eq(:mwahahaha)
       expect(new_resource).to be_updated
     end
diff --git a/spec/functional/rebooter_spec.rb b/spec/functional/rebooter_spec.rb
index 7630216..a0e2665 100644
--- a/spec/functional/rebooter_spec.rb
+++ b/spec/functional/rebooter_spec.rb
@@ -43,7 +43,7 @@ describe Chef::Platform::Rebooter do
 
   let(:expected) do
     {
-      :windows => 'shutdown /r /t 5 /c "rebooter spec test"',
+      :windows => 'shutdown /r /t 300 /c "rebooter spec test"',
       :linux => 'shutdown -r +5 "rebooter spec test"'
     }
   end
@@ -70,7 +70,7 @@ describe Chef::Platform::Rebooter do
 
       shared_context 'test a reboot method' do
         def test_rebooter_method(method_sym, is_windows, expected_reboot_str)
-          allow(Chef::Platform).to receive(:windows?).and_return(is_windows)
+          allow(ChefConfig).to receive(:windows?).and_return(is_windows)
           expect(rebooter).to receive(:shell_out!).once.with(expected_reboot_str)
           expect(rebooter).to receive(method_sym).once.and_call_original
           rebooter.send(method_sym, run_context.node)
diff --git a/spec/functional/resource/aix_service_spec.rb b/spec/functional/resource/aix_service_spec.rb
index 9dec87d..604c04d 100755
--- a/spec/functional/resource/aix_service_spec.rb
+++ b/spec/functional/resource/aix_service_spec.rb
@@ -108,8 +108,8 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
     it_behaves_like "src service"
   end
 
-
-  describe "When service is a group" do
+  # Cannot run this test on a WPAR
+  describe "When service is a group", :not_wpar do
     before(:all) do
       script_dir = File.join(File.dirname(__FILE__), "/../assets/")
       shell_out!("mkssys -s ctestsys -p #{script_dir}/testchefsubsys -u #{get_user_id} -S -n 15 -f 9 -R -Q -G ctestgrp")
diff --git a/spec/functional/resource/aixinit_service_spec.rb b/spec/functional/resource/aixinit_service_spec.rb
index 19b65ca..3d92161 100755
--- a/spec/functional/resource/aixinit_service_spec.rb
+++ b/spec/functional/resource/aixinit_service_spec.rb
@@ -208,4 +208,4 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
       end
     end
   end
-end
\ No newline at end of file
+end
diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb
index e5f5341..4bce309 100644
--- a/spec/functional/resource/deploy_revision_spec.rb
+++ b/spec/functional/resource/deploy_revision_spec.rb
@@ -819,7 +819,7 @@ describe Chef::Resource::DeployRevision, :unix_only => true do
       end
 
       before do
-        expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Chef::Exceptions::Exec)
+        expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
         deploy_to_latest_with_callback_tracking.run_action(:deploy)
       end
 
diff --git a/spec/functional/resource/dpkg_package_spec.rb b/spec/functional/resource/dpkg_package_spec.rb
new file mode 100644
index 0000000..aebe247
--- /dev/null
+++ b/spec/functional/resource/dpkg_package_spec.rb
@@ -0,0 +1,339 @@
+#
+# Copyright:: Copyright (c) 2014-2015 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 'spec_helper'
+require 'chef/mixin/shell_out'
+
+describe Chef::Resource::DpkgPackage, :requires_root, :debian_family_only, arch: "x86_64" do
+  include Chef::Mixin::ShellOut
+
+  let(:apt_data) { File.join(CHEF_SPEC_DATA, "apt") }
+
+  let(:test1_0) { File.join(apt_data, "chef-integration-test_1.0-1_amd64.deb") }
+  let(:test1_1) { File.join(apt_data, "chef-integration-test_1.1-1_amd64.deb") }
+  let(:test2_0) { File.join(apt_data, "chef-integration-test2_1.0-1_amd64.deb") }
+
+  let(:run_context) {
+    node = TEST_NODE.dup
+    events = Chef::EventDispatch::Dispatcher.new
+    Chef::RunContext.new(node, {}, events)
+  }
+
+  let(:dpkg_package) { Chef::Resource::DpkgPackage.new(test1_0, run_context)}
+
+  before(:each) do
+    shell_out("dpkg -P chef-integration-test chef-integration-test2")
+  end
+
+  # handles setting the name property after the initializer runs
+  def set_dpkg_package_name(name)
+    dpkg_package.name name
+    dpkg_package.package_name name
+  end
+
+  def should_be_purged_or_removed(package, action=nil)
+    status = shell_out("dpkg -s #{package}")
+    output = status.stdout + status.stderr
+    if action.nil? || action == :purge
+      expect(output).to match(/no info|not-installed|not installed/)
+    elsif action == :remove
+      expect(output).to match(/deinstall ok config-files/)
+    else
+      raise "Unknown action"
+    end
+  end
+
+  shared_examples_for "common behavior for upgrade or install" do
+    it "installs a package when given only the filename as a name argument (no source)" do
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test')
+    end
+
+    it "installs a package when given the name and a source argument" do
+      set_dpkg_package_name "chef-integration-test"
+      dpkg_package.source test1_0
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test')
+    end
+
+    it "installs a package when given a different name and a source argument" do
+      set_dpkg_package_name "some other name"
+      dpkg_package.source test1_0
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test')
+    end
+
+    it "installs a package when given a path as a package_name and no source" do
+      set_dpkg_package_name "chef-integration-test"
+      dpkg_package.package_name test1_0
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test')
+    end
+
+    it "raises an error when the name is not a path and the source is not given" do
+      set_dpkg_package_name "chef-integration-test"
+      dpkg_package.package_name "chef-integration-test"
+      expect { dpkg_package.run_action(action) }.to raise_error(Chef::Exceptions::Package)
+    end
+
+    it "raises an error when passed a package_name that does not exist" do
+      set_dpkg_package_name File.join(test1_0, "make.it.fail")
+      expect { dpkg_package.run_action(action) }.to raise_error(Chef::Exceptions::Package)
+    end
+
+    it "raises an error when passed a source that does not exist" do
+      set_dpkg_package_name "chef-integration-test"
+      dpkg_package.source File.join(test1_0, "make.it.fail")
+      expect { dpkg_package.run_action(action) }.to raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should not install an already installed package" do
+      shell_out!("dpkg -i #{test1_0}")
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test')
+    end
+
+    it "should handle a multipackage install" do
+      set_dpkg_package_name [ test1_0, test2_0 ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test')
+      shell_out!('dpkg -s chef-integration-test2')
+    end
+
+    it "should not update multipackages that are up-to-date" do
+      shell_out!("dpkg -i #{test1_0} #{test2_0}")
+      set_dpkg_package_name [ test1_0, test2_0 ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test')
+      shell_out!('dpkg -s chef-integration-test2')
+    end
+
+    it "should install the second if the first is installed" do
+      shell_out!("dpkg -i #{test1_0}")
+      set_dpkg_package_name [ test1_0, test2_0 ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test')
+      shell_out!('dpkg -s chef-integration-test2')
+    end
+
+    it "should install the first if the second is installed" do
+      shell_out!("dpkg -i #{test2_0}")
+      set_dpkg_package_name [ test1_0, test2_0 ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test')
+      shell_out!('dpkg -s chef-integration-test2')
+    end
+  end
+
+  context "action :install" do
+    let(:action) { :install }
+    it_behaves_like "common behavior for upgrade or install"
+
+    it "should not upgrade a package" do
+      shell_out!("dpkg -i #{test1_0}")
+      set_dpkg_package_name test1_1
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+    end
+
+    it "should not upgrade on a multipackage install" do
+      shell_out!("dpkg -i #{test1_0} #{test2_0}")
+      set_dpkg_package_name [ test1_1, test2_0 ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+    end
+  end
+
+  context "action :upgrade" do
+    let(:action) { :upgrade }
+    it_behaves_like "common behavior for upgrade or install"
+
+    it "should upgrade a package" do
+      shell_out!("dpkg -i #{test1_0}")
+      set_dpkg_package_name test1_1
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+    end
+
+    it "should upgrade on a multipackage install" do
+      shell_out!("dpkg -i #{test1_0} #{test2_0}")
+      set_dpkg_package_name [ test1_1, test2_0 ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+    end
+  end
+
+  shared_examples_for "common behavior for remove or purge" do
+    it "should remove a package that is installed when the name is a source" do
+      shell_out!("dpkg -i #{test1_0}")
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should do nothing if the package is not installed when the name is a source" do
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should remove a package that is installed when the name is the package name and source is nil" do
+      shell_out!("dpkg -i #{test1_0}")
+      set_dpkg_package_name "chef-integration-test"
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should do nothing if the package is not installed when the name is the package name and the source is nil" do
+      set_dpkg_package_name "chef-integration-test"
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should remove a package that is installed when the name is changed but the source is a package" do
+      shell_out!("dpkg -i #{test1_0}")
+      set_dpkg_package_name "some other name"
+      dpkg_package.source test1_0
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should do nothing if the package is not installed when the name is changed but the source is a package" do
+      set_dpkg_package_name "some other name"
+      dpkg_package.source test1_0
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should remove a package if the name is a file that does not exist, but the source exists" do
+      shell_out!("dpkg -i #{test1_0}")
+      dpkg_package.name "whatever"
+      dpkg_package.package_name File.join(test1_0, "make.it.fail")
+      dpkg_package.source test1_0
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should do nothing if the package is not installed when the name is a file that does not exist, but the source exists" do
+      set_dpkg_package_name "some other name"
+      dpkg_package.name "whatever"
+      dpkg_package.package_name File.join(test1_0, "make.it.fail")
+      dpkg_package.source test1_0
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should remove a package if the package_name is correct, but the source does not exist" do
+      shell_out!("dpkg -i #{test1_0}")
+      dpkg_package.name "whatever"
+      dpkg_package.package_name "chef-integration-test"
+      dpkg_package.source File.join(test1_0, "make.it.fail")
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should do nothing if the package_name is correct, but the source does not exist, and the package is not installed" do
+      dpkg_package.name "whatever"
+      dpkg_package.package_name "chef-integration-test"
+      dpkg_package.source File.join(test1_0, "make.it.fail")
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+    end
+
+    it "should remove both packages when called with two" do
+      shell_out!("dpkg -i #{test1_0} #{test2_0}")
+      set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+      should_be_purged_or_removed('chef-integration-test2', action)
+    end
+
+    it "should remove a package when only the first one is installed" do
+      shell_out!("dpkg -i #{test1_0}")
+      set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+      should_be_purged_or_removed('chef-integration-test2')
+    end
+
+    it "should remove a package when only the second one is installed" do
+      shell_out!("dpkg -i #{test2_0}")
+      set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+      should_be_purged_or_removed('chef-integration-test2', action)
+    end
+
+    it "should do nothing when both packages are not installed" do
+      set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ]
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test')
+      should_be_purged_or_removed('chef-integration-test2')
+    end
+  end
+
+  context "action :remove" do
+    let(:action) { :remove }
+    it_behaves_like "common behavior for remove or purge"
+
+    it "should not remove a removed package when the name is a source" do
+      # the "test2" file has a conffile declared in it
+      shell_out!("dpkg -i #{test2_0}")
+      shell_out!("dpkg -r chef-integration-test2")
+      set_dpkg_package_name "chef-integration-test2"
+      dpkg_package.run_action(action)
+      expect(dpkg_package).not_to be_updated_by_last_action
+      shell_out!('dpkg -s chef-integration-test2')  # its still 'installed'
+    end
+  end
+
+  context "action :purge" do
+    let(:action) { :purge }
+    it_behaves_like "common behavior for remove or purge"
+
+    it "should purge a removed package when the name is a source" do
+      # the "test2" file has a conffile declared in it
+      shell_out!("dpkg -i #{test2_0}")
+      shell_out!("dpkg -r chef-integration-test2")
+      set_dpkg_package_name "chef-integration-test2"
+      dpkg_package.run_action(action)
+      expect(dpkg_package).to be_updated_by_last_action
+      should_be_purged_or_removed('chef-integration-test2', action)
+    end
+  end
+end
diff --git a/spec/functional/resource/dsc_resource_spec.rb b/spec/functional/resource/dsc_resource_spec.rb
index 6f453ee..24503f1 100644
--- a/spec/functional/resource/dsc_resource_spec.rb
+++ b/spec/functional/resource/dsc_resource_spec.rb
@@ -43,6 +43,8 @@ describe Chef::Resource::DscResource, :windows_powershell_dsc_only do
     before do
       if !Chef::Platform.supports_dsc_invoke_resource?(node)
         skip 'Requires Powershell >= 5.0.10018.0'
+      elsif !Chef::Platform.dsc_refresh_mode_disabled?(node)
+        skip 'Requires LCM RefreshMode is Disabled'
       end
     end
     context 'with an invalid dsc resource' do
diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb
index a736949..dc77044 100644
--- a/spec/functional/resource/dsc_script_spec.rb
+++ b/spec/functional/resource/dsc_script_spec.rb
@@ -19,6 +19,7 @@
 require 'spec_helper'
 require 'chef/mixin/shell_out'
 require 'chef/mixin/windows_architecture_helper'
+require 'support/shared/integration/integration_helper'
 
 describe Chef::Resource::DscScript, :windows_powershell_dsc_only do
   include Chef::Mixin::WindowsArchitectureHelper
@@ -67,8 +68,7 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do
     node = Chef::Node.new
     node.automatic['platform'] = 'windows'
     node.automatic['platform_version'] = '6.1'
-    node.automatic['kernel'][:machine] =
-      is_i386_process_on_x86_64_windows? ? :x86_64 : :i386
+    node.automatic['kernel'][:machine] = :x86_64  # Only 64-bit architecture is supported
     node.automatic[:languages][:powershell][:version] = '4.0'
     empty_events = Chef::EventDispatch::Dispatcher.new
     Chef::RunContext.new(node, {}, empty_events)
@@ -379,4 +379,93 @@ EOH
     it_behaves_like 'a dsc_script with configuration data that takes parameters'
     it_behaves_like 'a dsc_script without configuration data that takes parameters'
   end
+
+  context 'when using ps_credential' do
+    include IntegrationSupport
+
+    before(:each) do
+      delete_user(dsc_user)
+      ohai_reader = Ohai::System.new
+      ohai_reader.all_plugins(["platform", "os", "languages/powershell"])
+      dsc_test_run_context.node.consume_external_attrs(ohai_reader.data,{})
+    end
+
+    let(:configuration_data_path) { 'C:\\configurationdata.psd1' }
+
+    let(:self_signed_cert_path) do
+      File.join(CHEF_SPEC_DATA, 'dsc_lcm.pfx')
+    end
+
+    let(:dsc_configuration_script) do
+      <<-MYCODE
+cd c:\\
+configuration LCM
+{
+  param ($thumbprint)
+  localconfigurationmanager
+  {
+    RebootNodeIfNeeded = $false
+    ConfigurationMode = 'ApplyOnly'
+    CertificateID = $thumbprint
+  }
+}
+$cert = ls Cert:\\LocalMachine\\My\\ |
+  Where-Object {$_.Subject -match "ChefTest"} |
+  Select -first 1
+
+if($cert -eq $null) {
+  $pfxpath = '#{self_signed_cert_path}'
+  $password = ''
+  $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxpath, $password, ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeyset))
+  $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "My", ([System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine)
+  $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
+  $store.Add($cert)
+  $store.Close()
+}
+
+lcm -thumbprint $cert.thumbprint
+set-dsclocalconfigurationmanager -path ./LCM  
+$ConfigurationData = @"
+@{
+AllNodes = @( 
+  @{  
+  NodeName = "localhost"; 
+  CertificateID = '$($cert.thumbprint)';
+  };
+);
+}
+"@
+$ConfigurationData | out-file '#{configuration_data_path}' -force
+  MYCODE
+    end
+
+    let(:powershell_script_resource) do
+      Chef::Resource::PowershellScript.new('configure-lcm', dsc_test_run_context).tap do |r|
+        r.code(dsc_configuration_script)
+        r.architecture(:x86_64)
+      end
+    end
+
+    let(:dsc_script_resource) do
+      dsc_test_resource_base.tap do |r|
+        r.code <<-EOF
+User dsctestusercreate
+{
+    UserName = '#{dsc_user}'
+    Password = #{r.ps_credential('jf9a8m49jrajf4#')}
+    Ensure = "Present"
+}
+EOF
+        r.configuration_data_script(configuration_data_path)
+      end
+    end
+
+    it 'allows the use of ps_credential' do
+      expect(user_exists?(dsc_user)).to eq(false)
+      powershell_script_resource.run_action(:run)
+      expect(File).to exist(configuration_data_path)
+      dsc_script_resource.run_action(:run)
+      expect(user_exists?(dsc_user)).to eq(true)
+    end
+  end
 end
diff --git a/spec/functional/resource/execute_spec.rb b/spec/functional/resource/execute_spec.rb
index ffa4628..692ccfb 100644
--- a/spec/functional/resource/execute_spec.rb
+++ b/spec/functional/resource/execute_spec.rb
@@ -137,9 +137,16 @@ describe Chef::Resource::Execute do
     end
   end
 
+  # Ensure that CommandTimeout is raised, and is caused by resource.timeout really expiring.
+  # https://github.com/chef/chef/issues/2985
+  #
+  # resource.timeout should be short, this is what we're testing
+  # resource.command ruby sleep timer should be longer than resource.timeout to give us something to timeout
+  # Timeout::timeout should be longer than resource.timeout, but less than the resource.command ruby sleep timer,
+  #   so we fail if we finish on resource.command instead of resource.timeout, but raise CommandTimeout anyway (#2175).
   it "times out when a timeout is set on the resource" do
-    Timeout::timeout(5) do
-      resource.command %{ruby -e 'sleep 600'}
+    Timeout::timeout(30) do
+      resource.command %{ruby -e 'sleep 300'}
       resource.timeout 0.1
       expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::CommandTimeout)
     end
diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb
index f1a290d..9e30e62 100644
--- a/spec/functional/resource/file_spec.rb
+++ b/spec/functional/resource/file_spec.rb
@@ -86,6 +86,31 @@ describe Chef::Resource::File do
     end
   end
 
+
+  describe "when using backup" do
+    before do
+      Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+      resource_without_content.backup(1)
+      resource_without_content.run_action(:create)
+    end
+
+    let(:backup_glob) { File.join(CHEF_SPEC_BACKUP_PATH, test_file_dir.sub(/^([A-Za-z]:)/, ""), "#{file_base}*") }
+
+    let(:path) do
+      # Use native system path
+      ChefConfig::PathHelper.canonical_path(File.join(test_file_dir, make_tmpname(file_base)), false)
+    end
+
+    it "only stores the number of requested backups" do
+      resource_without_content.content('foo')
+      resource_without_content.run_action(:create)
+      resource_without_content.content('bar')
+      resource_without_content.run_action(:create)
+      expect(Dir.glob(backup_glob).length).to eq(1)
+    end
+
+  end
+
   # github issue 1842.
   describe "when running action :create on a relative path" do
     before do
diff --git a/spec/functional/resource/group_spec.rb b/spec/functional/resource/group_spec.rb
index 6676aa3..0862b8e 100644
--- a/spec/functional/resource/group_spec.rb
+++ b/spec/functional/resource/group_spec.rb
@@ -95,7 +95,7 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
 
   def create_user(username)
     user(username).run_action(:create) if ! windows_domain_user?(username)
-    # TODO: User shouldn't exist
+    # TODO: User should exist
   end
 
   def remove_user(username)
@@ -135,45 +135,76 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
       group_should_not_exist(group_name)
     end
 
-    describe "when append is not set" do
-      let(:included_members) { [spec_members[1]] }
+    # dscl doesn't perform any error checking and will let you add users that don't exist.
+    describe "when no users exist", :not_supported_on_mac_osx do
+      describe "when append is not set" do
+        # excluded_members can only be used when append is set.  It is ignored otherwise.
+        let(:excluded_members) { [] }
 
-      before do
-        create_user(spec_members[1])
-        create_user(spec_members[0])
-        add_members_to_group([spec_members[0]])
-      end
-
-      after do
-        remove_user(spec_members[1])
-        remove_user(spec_members[0])
+        it "should raise an error" do
+          expect { group_resource.run_action(tested_action) }.to raise_error()
+        end
       end
 
-      it "should remove the existing users and add the new users to the group" do
-        group_resource.run_action(tested_action)
+      describe "when append is set" do
+        before do
+          group_resource.append(true)
+        end
 
-        expect(user_exist_in_group?(spec_members[1])).to eq(true)
-        expect(user_exist_in_group?(spec_members[0])).to eq(false)
+        it "should raise an error" do
+          expect { group_resource.run_action(tested_action) }.to raise_error()
+        end
       end
     end
 
-    describe "when append is set" do
-      before(:each) do
-        group_resource.append(true)
+    describe "when the users exist" do
+      before do
+        (spec_members).each do |member|
+          create_user(member)
+        end
       end
 
-      describe "when the users exist" do
-        before do
-          (included_members + excluded_members).each do |member|
-            create_user(member)
+      after do
+        (spec_members).each do |member|
+          remove_user(member)
+        end
+      end
+
+      describe "when append is not set" do
+        it "should set the group to to contain given members" do
+          group_resource.run_action(tested_action)
+
+          included_members.each do |member|
+            expect(user_exist_in_group?(member)).to eq(true)
+          end
+          (spec_members - included_members).each do |member|
+            expect(user_exist_in_group?(member)).to eq(false)
           end
         end
 
-        after do
-          (included_members + excluded_members).each do |member|
-            remove_user(member)
+        describe "when group already contains some users" do
+          before do
+            add_members_to_group([included_members[0]])
+            add_members_to_group(spec_members - included_members)
+          end
+
+          it "should remove all existing users and only add the new users to the group" do
+            group_resource.run_action(tested_action)
+
+            included_members.each do |member|
+              expect(user_exist_in_group?(member)).to eq(true)
+            end
+            (spec_members - included_members).each do |member|
+              expect(user_exist_in_group?(member)).to eq(false)
+            end
           end
         end
+      end
+
+      describe "when append is set" do
+        before(:each) do
+          group_resource.append(true)
+        end
 
         it "should add included members to the group" do
           group_resource.run_action(tested_action)
@@ -186,9 +217,9 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
           end
         end
 
-        describe "when group contains some users" do
+        describe "when group already contains some users" do
           before(:each) do
-            add_members_to_group([ spec_members[0], spec_members[2] ])
+            add_members_to_group([included_members[0], excluded_members[0]])
           end
 
           it "should add the included users and remove excluded users" do
@@ -203,20 +234,6 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
           end
         end
       end
-
-      describe "when the users doesn't exist" do
-        describe "when append is not set" do
-          it "should raise an error" do
-            expect { @grp_resource.run_action(tested_action) }.to raise_error
-          end
-        end
-
-        describe "when append is set" do
-          it "should raise an error" do
-            expect { @grp_resource.run_action(tested_action) }.to raise_error
-          end
-        end
-      end
     end
   end
 
@@ -231,6 +248,12 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
       group_should_exist(group_name)
     end
 
+    after(:each) do
+      group_resource.run_action(:remove)
+    end
+
+    # TODO: The ones below might actually return ArgumentError now - but I don't have
+    # a way to verify that.  Change it and delete this comment if that's the case.
     describe "when updating membership" do
       it "raises an error for a non well-formed domain name" do
         group_resource.members [invalid_domain_user_name]
@@ -256,7 +279,7 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
     end
   end
 
-  let(:group_name) { "t-#{SecureRandom.random_number(9999)}" }
+  let(:group_name) { "group#{SecureRandom.random_number(9999)}" }
   let(:included_members) { nil }
   let(:excluded_members) { nil }
   let(:group_resource) {
@@ -300,7 +323,7 @@ theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking\
 downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" }
 
       it "should not create a group" do
-        expect { group_resource.run_action(:create) }.to raise_error
+        expect { group_resource.run_action(:create) }.to raise_error(ArgumentError)
         group_should_not_exist(group_name)
       end
     end
@@ -372,6 +395,11 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" }
     let(:tested_action) { :manage }
 
     describe "when there is no group" do
+      before(:each) do
+        group_resource.run_action(:remove)
+        group_should_not_exist(group_name)
+      end
+
       it "raises an error on modify" do
         expect { group_resource.run_action(:modify) }.to raise_error
       end
diff --git a/spec/functional/resource/ifconfig_spec.rb b/spec/functional/resource/ifconfig_spec.rb
index 9c61354..4733b05 100644
--- a/spec/functional/resource/ifconfig_spec.rb
+++ b/spec/functional/resource/ifconfig_spec.rb
@@ -22,7 +22,9 @@ require 'chef/mixin/shell_out'
 # run this test only for following platforms.
 include_flag = !(['ubuntu', 'centos', 'aix'].include?(ohai[:platform]))
 
-describe Chef::Resource::Ifconfig, :requires_root, :external => include_flag do
+describe Chef::Resource::Ifconfig, :requires_root, :skip_travis, :external => include_flag do
+  # This test does not work in travis because there is no eth0
+
   include Chef::Mixin::ShellOut
 
   let(:new_resource) do
diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb
index d39a0c2..7e903b3 100644
--- a/spec/functional/resource/link_spec.rb
+++ b/spec/functional/resource/link_spec.rb
@@ -348,8 +348,7 @@ describe Chef::Resource::Link do
           end
           it_behaves_like 'delete errors out'
         end
-        context 'and the link already exists and is not writeable to this user', :skip => true do
-        end
+
         it_behaves_like 'a securable resource without existing target' do
           let(:path) { target_file }
           def allowed_acl(sid, expected_perms)
@@ -360,7 +359,7 @@ describe Chef::Resource::Link do
           end
           def parent_inheritable_acls
             dummy_file_path = File.join(test_file_dir, "dummy_file")
-            dummy_file = FileUtils.touch(dummy_file_path)
+            FileUtils.touch(dummy_file_path)
             dummy_desc = get_security_descriptor(dummy_file_path)
             FileUtils.rm_rf(dummy_file_path)
             dummy_desc
@@ -416,8 +415,6 @@ describe Chef::Resource::Link do
           end
         end
       end
-      context "when the link destination is not readable to this user", :skip => true do
-      end
       context "when the link destination does not exist" do
         include_context 'create symbolic link succeeds'
         include_context 'delete is noop'
@@ -518,8 +515,6 @@ describe Chef::Resource::Link do
           end
           it_behaves_like 'delete errors out'
         end
-        context "and the link already exists and is not writeable to this user", :skip => true do
-        end
         context "and specifies security attributes" do
           before(:each) do
             resource.owner(windows? ? 'Guest' : 'nobody')
@@ -559,10 +554,10 @@ describe Chef::Resource::Link do
           end
           context 'and the link does not yet exist' do
             it 'links to the target file' do
+              skip('OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks') if (os_x? or freebsd? or aix?)
               resource.run_action(:create)
               expect(File.exists?(target_file)).to be_truthy
               # OS X gets angry about this sort of link.  Bug in OS X, IMO.
-              pending('OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks') if (os_x? or freebsd? or aix?)
               expect(symlink?(target_file)).to be_truthy
               expect(readlink(target_file)).to eq(canonicalize(@other_target))
             end
@@ -578,7 +573,7 @@ describe Chef::Resource::Link do
           end
           context 'and the link does not yet exist' do
             it 'links to the target file' do
-              pending('OS X/FreeBSD/AIX fails to create hardlinks to broken symlinks') if (os_x? or freebsd? or aix?)
+              skip('OS X/FreeBSD/AIX fails to create hardlinks to broken symlinks') if (os_x? or freebsd? or aix?)
               resource.run_action(:create)
               # Windows and Unix have different definitions of exists? here, and that's OK.
               if windows?
@@ -593,8 +588,7 @@ describe Chef::Resource::Link do
           end
         end
       end
-      context "when the link destination is not readable to this user", :skip => true do
-      end
+
       context "when the link destination does not exist" do
         context 'and the link does not yet exist' do
           it 'create errors out' do
diff --git a/spec/functional/resource/mount_spec.rb b/spec/functional/resource/mount_spec.rb
index a016d12..cb3fa45 100644
--- a/spec/functional/resource/mount_spec.rb
+++ b/spec/functional/resource/mount_spec.rb
@@ -21,9 +21,11 @@ require 'chef/mixin/shell_out'
 require 'tmpdir'
 
 # run this test only for following platforms.
-include_flag = !(['ubuntu', 'centos', 'aix', 'solaris2'].include?(ohai[:platform]))
+include_flag = !(['ubuntu', 'centos', 'solaris2'].include?(ohai[:platform]))
 
-describe Chef::Resource::Mount, :requires_root, :external => include_flag do
+describe Chef::Resource::Mount, :requires_root, :skip_travis, :external => include_flag do
+  # Disabled in travis because it refuses to let us mount a ramdisk. /dev/ramX does not
+  # exist even after loading the kernel module
 
   include Chef::Mixin::ShellOut
 
@@ -32,6 +34,7 @@ describe Chef::Resource::Mount, :requires_root, :external => include_flag do
   def setup_device_for_mount
     # use ramdisk for creating a test device for mount.
     # This can cleaner if we have chef resource/provider for ramdisk.
+    # TODO: These tests only work in LPARs, not WPARs on AIX.
     case ohai[:platform]
     when "aix"
       ramdisk = shell_out!("mkramdisk 16M").stdout
diff --git a/spec/functional/resource/package_spec.rb b/spec/functional/resource/package_spec.rb
index 5c17ca0..25ec872 100644
--- a/spec/functional/resource/package_spec.rb
+++ b/spec/functional/resource/package_spec.rb
@@ -1,7 +1,7 @@
 # encoding: UTF-8
 #
 # Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# Copyright:: Copyright (c) 2013-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -386,5 +386,3 @@ describe Chef::Resource::Package, metadata do
   end
 
 end
-
-
diff --git a/spec/functional/resource/powershell_spec.rb b/spec/functional/resource/powershell_script_spec.rb
similarity index 73%
rename from spec/functional/resource/powershell_spec.rb
rename to spec/functional/resource/powershell_script_spec.rb
index 56a905e..91b74fd 100644
--- a/spec/functional/resource/powershell_spec.rb
+++ b/spec/functional/resource/powershell_script_spec.rb
@@ -16,6 +16,7 @@
 # limitations under the License.
 #
 
+require 'chef/platform/query_helpers'
 require 'spec_helper'
 
 describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
@@ -27,7 +28,6 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
 
   it_behaves_like "a Windows script running on Windows"
 
-
   let(:successful_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe $env:systemroot" }
   let(:failed_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe /badargument" }
   let(:processor_architecture_script_content) { "echo $env:PROCESSOR_ARCHITECTURE" }
@@ -56,14 +56,15 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       resource.run_action(:run)
     end
 
-    it "returns the -27 for a powershell script that exits with -27", :windows_powershell_dsc_only do
-      # This is broken on Powershell < 4.0
+    it "returns the exit status 27 for a powershell script that exits with 27" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       file = Tempfile.new(['foo', '.ps1'])
       begin
-        file.write "exit -27"
+        file.write "exit 27"
         file.close
         resource.code(". \"#{file.path}\"")
-        resource.returns(-27)
+        resource.returns(27)
         resource.run_action(:run)
       ensure
         file.close
@@ -71,8 +72,36 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       end
     end
 
+    let (:negative_exit_status) { -27 }
+    let (:unsigned_exit_status) { (-negative_exit_status ^ 65535) + 1 }
+    it "returns the exit status -27 as a signed integer or an unsigned 16-bit 2's complement value of 65509 for a powershell script that exits with -27" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
+      # Versions of PowerShell prior to 4.0 return a 16-bit unsigned value --
+      # PowerShell 4.0 and later versions return a 32-bit signed value.
+      file = Tempfile.new(['foo', '.ps1'])
+      begin
+        file.write "exit #{negative_exit_status.to_s}"
+        file.close
+        resource.code(". \"#{file.path}\"")
+
+        # PowerShell earlier than 4.0 takes negative exit codes
+        # and returns them as the underlying unsigned 16-bit
+        # 2's complement representation. We cover multiple versions
+        # of PowerShell in this example by including both the signed
+        # exit code and its converted counterpart as permitted return values.
+        # See http://support.microsoft.com/en-us/kb/2646183/zh-cn
+        resource.returns([negative_exit_status, unsigned_exit_status])
+        expect { resource.run_action(:run) }.not_to raise_error
+      ensure
+        file.close
+        file.unlink
+      end
+    end
 
     it "returns the process exit code" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       resource.code(arbitrary_nonzero_process_exit_code_content)
       resource.returns(arbitrary_nonzero_process_exit_code)
       resource.run_action(:run)
@@ -91,15 +120,37 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
     end
 
     it "returns 1 if the last command was a cmdlet that failed" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       resource.code(cmdlet_exit_code_not_found_content)
       resource.returns(1)
       resource.run_action(:run)
     end
 
     it "returns 1 if the last command was a cmdlet that failed and was preceded by a successfully executed non-cmdlet Windows binary" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       resource.code([windows_process_exit_code_success_content, cmdlet_exit_code_not_found_content].join(';'))
       resource.returns(1)
-      resource.run_action(:run)
+      expect { resource.run_action(:run) }.not_to raise_error
+    end
+
+    it "raises a Mixlib::ShellOut::ShellCommandFailed error if the script is not syntactically correct" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
+      resource.code('if({)')
+      resource.returns(0)
+      expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
+    end
+
+    it "raises an error if the script is not syntactically correct even if returns is set to 1 which is what powershell.exe returns for syntactically invalid scripts" do
+      # This test fails because shell_out expects the exit status to be 1, but it is actually 0
+      # The error is a false-positive.
+      skip "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
+      resource.code('if({)')
+      resource.returns(1)
+      expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
     end
 
     # This somewhat ambiguous case, two failures of different types,
@@ -111,24 +162,32 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
     # errors than 0 or 1, we return that instead, which is acceptable
     # since callers can test for nonzero rather than testing for 1.
     it "returns 1 if the last command was a cmdlet that failed and was preceded by an unsuccessfully executed non-cmdlet Windows binary" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       resource.code([arbitrary_nonzero_process_exit_code_content,cmdlet_exit_code_not_found_content].join(';'))
       resource.returns(arbitrary_nonzero_process_exit_code)
       resource.run_action(:run)
     end
 
     it "returns 0 if the last command was a non-cmdlet Windows binary that succeeded and was preceded by a failed cmdlet" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(';'))
       resource.returns(arbitrary_nonzero_process_exit_code)
       resource.run_action(:run)
     end
 
     it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that succeeded" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(';'))
       resource.returns(arbitrary_nonzero_process_exit_code)
       resource.run_action(:run)
     end
 
     it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that failed" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       resource.code([cmdlet_exit_code_not_found_content, arbitrary_nonzero_process_exit_code_content].join(';'))
       resource.returns(arbitrary_nonzero_process_exit_code)
       resource.run_action(:run)
@@ -147,6 +206,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
     end
 
     it "returns 1 for $false as the last line of the script when convert_boolean_return is true" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       resource.convert_boolean_return true
       resource.code "$false"
       resource.returns(1)
@@ -173,6 +234,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
     end
 
     it "returns 1 if an invalid flag is passed to the interpreter" do
+      pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
       resource.code(cmdlet_exit_code_success_content)
       resource.flags(invalid_powershell_interpreter_flag)
       resource.returns(1)
@@ -192,10 +255,25 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       expect { resource.should_skip?(:run) }.to raise_error(ArgumentError, /guard_interpreter does not support blocks/)
     end
 
+    context "when dsc is supported", :windows_powershell_dsc_only do
+      it "can execute LCM configuration code" do
+        resource.code <<-EOF
+configuration LCM
+{
+  param ($thumbprint)
+  localconfigurationmanager
+  {
+    RebootNodeIfNeeded = $false
+    ConfigurationMode = 'ApplyOnly'
+  }
+}
+        EOF
+        expect { resource.run_action(:run) }.not_to raise_error
+      end
+    end
   end
 
-  context "when running on a 32-bit version of Windows", :windows32_only do
-
+  context "when running on a 32-bit version of Ruby", :ruby32_only do
     it "executes a script with a 32-bit process if process architecture :i386 is specified" do
       resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
       resource.architecture(:i386)
@@ -205,15 +283,28 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       expect(source_contains_case_insensitive_content?( get_script_output, 'x86' )).to eq(true)
     end
 
-    it "raises an exception if :x86_64 process architecture is specified" do
-      begin
-        expect(resource.architecture(:x86_64)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect
-      rescue Chef::Exceptions::Win32ArchitectureIncorrect
+    context "when running on a 64-bit version of Windows", :windows64_only do
+      it "executes a script with a 64-bit process if :x86_64 arch is specified" do
+        resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
+        resource.architecture(:x86_64)
+        resource.returns(0)
+        resource.run_action(:run)
+
+        expect(source_contains_case_insensitive_content?( get_script_output, 'AMD64' )).to eq(true)
+      end
+    end
+
+    context "when running on a 32-bit version of Windows", :windows32_only do
+      it "raises an exception if :x86_64 process architecture is specified" do
+        begin
+          expect(resource.architecture(:x86_64)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect
+        rescue Chef::Exceptions::Win32ArchitectureIncorrect
+        end
       end
     end
   end
 
-  context "when running on a 64-bit version of Windows", :windows64_only do
+  context "when running on a 64-bit version of Ruby", :ruby64_only do
     it "executes a script with a 64-bit process if :x86_64 arch is specified" do
       resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
       resource.architecture(:x86_64)
@@ -223,7 +314,7 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       expect(source_contains_case_insensitive_content?( get_script_output, 'AMD64' )).to eq(true)
     end
 
-    it "executes a script with a 32-bit process if :i386 arch is specified" do
+    it "executes a script with a 32-bit process if :i386 arch is specified", :not_supported_on_nano do
       resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
       resource.architecture(:i386)
       resource.returns(0)
@@ -231,6 +322,12 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
 
       expect(source_contains_case_insensitive_content?( get_script_output, 'x86' )).to eq(true)
     end
+
+    it "raises an error when executing a script with a 32-bit process on Windows Nano Server", :windows_nano_only do
+      resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
+      expect{ resource.architecture(:i386) }.to raise_error(Chef::Exceptions::Win32ArchitectureIncorrect,
+        "cannot execute script with requested architecture 'i386' on Windows Nano Server")
+    end
   end
 
   describe "when executing guards" do
@@ -284,6 +381,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       end
 
       it "evaluates a powershell $false for a not_if block as true" do
+        pending "powershell.exe always exits with $true on nano" if Chef::Platform.windows_nano_server?
+
         resource.not_if  "$false"
         expect(resource.should_skip?(:run)).to be_falsey
       end
@@ -294,6 +393,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       end
 
       it "evaluates a powershell $false for an only_if block as false" do
+        pending "powershell.exe always exits with $true on nano" if Chef::Platform.windows_nano_server?
+
         resource.only_if  "$false"
         expect(resource.should_skip?(:run)).to be_truthy
       end
@@ -314,6 +415,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       end
 
       it "evaluates a non-zero powershell exit status for not_if as true" do
+        pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
         resource.not_if  "exit 37"
         expect(resource.should_skip?(:run)).to be_falsey
       end
@@ -324,6 +427,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       end
 
       it "evaluates a failed executable exit status for not_if as false" do
+        pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server?
+
         resource.not_if  windows_process_exit_code_not_found_content
         expect(resource.should_skip?(:run)).to be_falsey
       end
@@ -334,6 +439,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       end
 
       it "evaluates a failed executable exit status for only_if as false" do
+        pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server?
+
         resource.only_if  windows_process_exit_code_not_found_content
         expect(resource.should_skip?(:run)).to be_truthy
       end
@@ -344,6 +451,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       end
 
       it "evaluates a failed cmdlet exit status for not_if as true" do
+        pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server?
+
         resource.not_if  "throw 'up'"
         expect(resource.should_skip?(:run)).to be_falsey
       end
@@ -354,6 +463,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       end
 
       it "evaluates a failed cmdlet exit status for only_if as false" do
+        pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server?
+
         resource.only_if  "throw 'up'"
         expect(resource.should_skip?(:run)).to be_truthy
       end
@@ -396,30 +507,36 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
       end
 
       it "evaluates a 64-bit resource with a 64-bit guard and interprets boolean true as nonzero status code", :windows64_only do
+        pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
         resource.architecture :x86_64
         resource.only_if  "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'AMD64')"
         expect(resource.should_skip?(:run)).to be_truthy
       end
 
-      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code" do
+      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code", :not_supported_on_nano do
         resource.architecture :i386
         resource.only_if  "exit [int32]($env:PROCESSOR_ARCHITECTURE -ne 'X86')"
         expect(resource.should_skip?(:run)).to be_falsey
       end
 
-      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code" do
+      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code", :not_supported_on_nano do
         resource.architecture :i386
         resource.only_if  "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'X86')"
         expect(resource.should_skip?(:run)).to be_truthy
       end
 
       it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for only_if" do
+        pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
         resource.convert_boolean_return true
         resource.only_if  "$false"
         expect(resource.should_skip?(:run)).to be_truthy
       end
 
       it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for not_if" do
+        pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
         resource.convert_boolean_return true
         resource.not_if  "$false"
         expect(resource.should_skip?(:run)).to be_falsey
@@ -437,33 +554,40 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
         expect(resource.should_skip?(:run)).to be_truthy
       end
 
-      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for only_if" do
+      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for only_if", :not_supported_on_nano do
         resource.convert_boolean_return true
         resource.architecture :i386
         resource.only_if  "$env:PROCESSOR_ARCHITECTURE -eq 'X86'"
         expect(resource.should_skip?(:run)).to be_falsey
       end
 
-      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for not_if" do
+      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for not_if", :not_supported_on_nano do
         resource.convert_boolean_return true
         resource.architecture :i386
         resource.not_if  "$env:PROCESSOR_ARCHITECTURE -ne 'X86'"
         expect(resource.should_skip?(:run)).to be_falsey
       end
 
-      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for only_if" do
+      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for only_if", :not_supported_on_nano do
         resource.convert_boolean_return true
         resource.architecture :i386
         resource.only_if  "$env:PROCESSOR_ARCHITECTURE -ne 'X86'"
         expect(resource.should_skip?(:run)).to be_truthy
       end
 
-      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for not_if" do
+      it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for not_if", :not_supported_on_nano do
         resource.convert_boolean_return true
         resource.architecture :i386
         resource.not_if  "$env:PROCESSOR_ARCHITECTURE -eq 'X86'"
         expect(resource.should_skip?(:run)).to be_truthy
       end
+
+      it "raises an error when a 32-bit guard is used on Windows Nano Server", :windows_nano_only do
+        resource.only_if "$true", :architecture => :i386
+        expect{resource.run_action(:run)}.to raise_error(
+          Chef::Exceptions::Win32ArchitectureIncorrect,
+          /cannot execute script with requested architecture 'i386' on Windows Nano Server/)
+      end
     end
   end
 
diff --git a/spec/functional/resource/user/useradd_spec.rb b/spec/functional/resource/user/useradd_spec.rb
index 3e4e4e7..474f6a4 100644
--- a/spec/functional/resource/user/useradd_spec.rb
+++ b/spec/functional/resource/user/useradd_spec.rb
@@ -65,8 +65,12 @@ describe Chef::Provider::User::Useradd, metadata do
     end
   end
 
-  def supports_quote_in_username?
-    OHAI_SYSTEM["platform_family"] == "debian"
+  def self.quote_in_username_unsupported?
+    if OHAI_SYSTEM["platform_family"] == "debian"
+      false
+    else
+      "Only debian family systems support quotes in username"
+    end
   end
 
   def password_should_be_set
@@ -108,7 +112,7 @@ describe Chef::Provider::User::Useradd, metadata do
         break if status.exitstatus != 8
 
         sleep 1
-        max_retries = max_retries -1
+        max_retries = max_retries - 1
       rescue UserNotFound
         break
       end
@@ -162,15 +166,10 @@ describe Chef::Provider::User::Useradd, metadata do
     end
   end
 
-  let(:skip) { false }
-
   describe "action :create" do
 
     context "when the user does not exist beforehand" do
       before do
-        if reason = skip
-          pending(reason)
-        end
         user_resource.run_action(:create)
         expect(user_resource).to be_updated_by_last_action
       end
@@ -186,14 +185,7 @@ describe Chef::Provider::User::Useradd, metadata do
       #  tabulation: '\t', etc.). Note that using a slash ('/') may break the
       #  default algorithm for the definition of the user's home directory.
 
-      context "and the username contains a single quote" do
-        let(:skip) do
-          if supports_quote_in_username?
-            false
-          else
-            "Platform #{OHAI_SYSTEM["platform"]} not expected to support username w/ quote"
-          end
-        end
+      context "and the username contains a single quote", skip: quote_in_username_unsupported? do
 
         let(:username) { "t'bilisi" }
 
@@ -342,7 +334,7 @@ describe Chef::Provider::User::Useradd, metadata do
 
       before do
         if reason = skip
-          pending(reason)
+          skip(reason)
         end
         existing_user.run_action(:create)
         expect(existing_user).to be_updated_by_last_action
@@ -535,7 +527,7 @@ describe Chef::Provider::User::Useradd, metadata do
 
     def aix_user_lock_status
       lock_info = shell_out!("lsuser -a account_locked #{username}")
-      status = /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)[1]
+      /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)[1]
     end
 
     def user_account_should_be_locked
diff --git a/spec/functional/resource/user/windows_spec.rb b/spec/functional/resource/user/windows_spec.rb
new file mode 100644
index 0000000..5e3a909
--- /dev/null
+++ b/spec/functional/resource/user/windows_spec.rb
@@ -0,0 +1,133 @@
+# Author:: Jay Mundrawala (<jdm at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software
+# 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 'spec_helper'
+require 'chef/mixin/shell_out'
+
+describe Chef::Provider::User::Windows, :windows_only do
+  include Chef::Mixin::ShellOut
+
+  let(:username) { 'ChefFunctionalTest' }
+  let(:password) { SecureRandom.uuid }
+
+  let(:node) do
+    n = Chef::Node.new
+    n.consume_external_attrs(OHAI_SYSTEM.data.dup, {})
+    n
+  end
+
+  let(:events) { Chef::EventDispatch::Dispatcher.new }
+  let(:run_context) { Chef::RunContext.new(node, {}, events) }
+  let(:new_resource) do
+    Chef::Resource::User.new(username, run_context).tap do |r|
+      r.provider(Chef::Provider::User::Windows)
+      r.password(password)
+    end
+  end
+
+  def delete_user(u)
+    shell_out("net user #{u} /delete")
+  end
+
+  before do
+    delete_user(username)
+  end
+
+  describe 'action :create' do
+    it 'creates a user when a username and password are given' do
+      new_resource.run_action(:create)
+      expect(new_resource).to be_updated_by_last_action
+      expect(shell_out("net user #{username}").exitstatus).to eq(0)
+    end
+
+    it 'reports no changes if there are no changes needed' do
+      new_resource.run_action(:create)
+      new_resource.run_action(:create)
+      expect(new_resource).not_to be_updated_by_last_action
+    end
+
+    it 'allows chaning the password' do
+      new_resource.run_action(:create)
+      new_resource.password(SecureRandom.uuid)
+      new_resource.run_action(:create)
+      expect(new_resource).to be_updated_by_last_action
+    end
+
+    context 'with a gid specified' do
+      it 'warns unsupported' do
+        expect(Chef::Log).to receive(:warn).with(/not implemented/)
+        new_resource.gid('agroup')
+        new_resource.run_action(:create)
+      end
+    end
+  end
+
+  describe 'action :remove' do
+    before do
+      new_resource.run_action(:create)
+    end
+
+    it 'deletes the user' do
+      new_resource.run_action(:remove)
+      expect(new_resource).to be_updated_by_last_action
+      expect(shell_out("net user #{username}").exitstatus).to eq(2)
+    end
+
+    it 'is idempotent' do
+      new_resource.run_action(:remove)
+      new_resource.run_action(:remove)
+      expect(new_resource).not_to be_updated_by_last_action
+    end
+  end
+
+  describe 'action :lock' do
+    before do
+      new_resource.run_action(:create)
+    end
+
+    it 'locks the user account' do
+      new_resource.run_action(:lock)
+      expect(new_resource).to be_updated_by_last_action
+      expect(shell_out("net user #{username}").stdout).to match(/Account active\s*No/)
+    end
+
+    it 'is idempotent' do
+      new_resource.run_action(:lock)
+      new_resource.run_action(:lock)
+      expect(new_resource).not_to be_updated_by_last_action
+    end
+  end
+
+  describe 'action :unlock' do
+    before do
+      new_resource.run_action(:create)
+      new_resource.run_action(:lock)
+    end
+
+    it 'unlocks the user account' do
+      new_resource.run_action(:unlock)
+      expect(new_resource).to be_updated_by_last_action
+      expect(shell_out("net user #{username}").stdout).to match(/Account active\s*Yes/)
+    end
+
+    it 'is idempotent' do
+      new_resource.run_action(:unlock)
+      new_resource.run_action(:unlock)
+      expect(new_resource).not_to be_updated_by_last_action
+    end
+  end
+end
diff --git a/spec/functional/resource/windows_package_spec.rb b/spec/functional/resource/windows_package_spec.rb
new file mode 100644
index 0000000..6537865
--- /dev/null
+++ b/spec/functional/resource/windows_package_spec.rb
@@ -0,0 +1,177 @@
+#
+# Author:: Matt Wrock (<matt at mattwrock.com>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'functional/resource/base'
+
+describe Chef::Resource::WindowsPackage, :windows_only, :volatile do
+  let(:pkg_name) { nil }
+  let(:pkg_path) { nil }
+  let(:pkg_checksum) { nil }
+  let(:pkg_version) { nil }
+  let(:pkg_type) { nil }
+  let(:pkg_options) { nil }
+
+  subject do
+    new_resource = Chef::Resource::WindowsPackage.new(pkg_name, run_context)
+    new_resource.source pkg_path
+    new_resource.version pkg_version
+    new_resource.installer_type pkg_type
+    new_resource.options pkg_options
+    new_resource.checksum pkg_checksum
+    new_resource
+  end
+
+  describe "multi package scenario" do
+    let(:pkg_name) { 'Microsoft Visual C++ 2005 Redistributable' }
+    let(:pkg_path) { 'https://download.microsoft.com/download/6/B/B/6BB661D6-A8AE-4819-B79F-236472F6070C/vcredist_x86.exe' }
+    let(:pkg_checksum) { nil }
+    let(:pkg_version) { '8.0.59193' }
+    let(:pkg_type) { :custom }
+    let(:pkg_options) { "/Q" }
+
+    it "updates resource on first install" do
+      subject.run_action(:install)
+      expect(subject).to be_updated_by_last_action
+    end
+
+    it "does not update resource when already installed" do
+      subject.run_action(:install)
+      expect(subject).not_to be_updated_by_last_action
+    end
+
+    context "installing additional version" do
+      let(:pkg_path) { 'https://download.microsoft.com/download/e/1/c/e1c773de-73ba-494a-a5ba-f24906ecf088/vcredist_x86.exe' }
+      let(:pkg_version) { '8.0.56336' }
+
+      it "installs older version" do
+        subject.run_action(:install)
+        expect(subject).to be_updated_by_last_action
+      end
+    end
+
+    describe "removing package" do
+      subject { Chef::Resource::WindowsPackage.new(pkg_name, run_context) }
+
+      context "multiple versions and a version given to remove" do
+        before { subject.version('8.0.56336')}
+
+        it "removes specified version" do
+          subject.run_action(:remove)
+          expect(subject).to be_updated_by_last_action
+          prov = subject.provider_for_action(:remove)
+          prov.load_current_resource
+          expect(prov.current_version_array).to eq([['8.0.59193']])
+        end
+      end
+
+      context "single version installed and no version given to remove" do
+        it "removes last remaining version" do
+          subject.run_action(:remove)
+          expect(subject).to be_updated_by_last_action
+          prov = subject.provider_for_action(:remove)
+          prov.load_current_resource
+          expect(prov.current_version_array).to eq([nil])
+        end
+      end
+
+      describe "removing multiple versions at once" do
+        let(:pkg_version) { nil }
+        before do
+          install1 = Chef::Resource::WindowsPackage.new(pkg_name, run_context)
+          install1.source pkg_path
+          install1.version pkg_version
+          install1.installer_type pkg_type
+          install1.options pkg_options
+          install1.run_action(:install)
+
+          install2 = Chef::Resource::WindowsPackage.new(pkg_name, run_context)
+          install2.source 'https://download.microsoft.com/download/e/1/c/e1c773de-73ba-494a-a5ba-f24906ecf088/vcredist_x86.exe'
+          install2.version '8.0.56336'
+          install2.installer_type pkg_type
+          install2.options pkg_options
+          install2.run_action(:install)
+        end
+
+        it "removes all versions" do
+          subject.run_action(:remove)
+          expect(subject).to be_updated_by_last_action
+          prov = subject.provider_for_action(:remove)
+          prov.load_current_resource
+          expect(prov.current_version_array).to eq([nil])
+        end
+      end
+    end
+  end
+
+  describe "package version and installer type" do
+    after { subject.run_action(:remove) }
+
+    context "null soft" do
+      let(:pkg_name) { 'Ultra Defragmenter' }
+      let(:pkg_path) { 'http://iweb.dl.sourceforge.net/project/ultradefrag/stable-release/6.1.1/ultradefrag-6.1.1.bin.amd64.exe' }
+      let(:pkg_checksum) { '11d53ed4c426c8c867ad43f142b7904226ffd9938c02e37086913620d79e3c09' }
+      
+      it "finds the correct package version" do
+        subject.run_action(:install)
+        expect(subject.version).to eq('6.1.1')
+      end
+
+      it "finds the correct installer type" do
+        subject.run_action(:install)
+        expect(subject.provider_for_action(:install).installer_type).to eq(:nsis)
+      end
+    end
+
+    context "inno" do
+      let(:pkg_name) { 'Mercurial 3.6.1 (64-bit)' }
+      let(:pkg_path) { 'http://mercurial.selenic.com/release/windows/Mercurial-3.6.1-x64.exe' }
+      let(:pkg_checksum) { 'febd29578cb6736163d232708b834a2ddd119aa40abc536b2c313fc5e1b5831d' }
+
+      it "finds the correct package version" do
+        subject.run_action(:install)
+        expect(subject.version).to eq(nil) # Mercurial does not include versioning
+      end
+
+      it "finds the correct installer type" do
+        subject.run_action(:install)
+        expect(subject.provider_for_action(:install).installer_type).to eq(:inno)
+      end
+    end
+  end
+
+  describe "install from local file" do
+    let(:pkg_name) { 'Mercurial 3.6.1 (64-bit)' }
+    let(:pkg_path) { ::File.join(Chef::Config[:file_cache_path], "package", "Mercurial-3.6.1-x64.exe") }
+    let(:pkg_checksum) { 'febd29578cb6736163d232708b834a2ddd119aa40abc536b2c313fc5e1b5831d' }
+
+    it "installs the app" do
+      subject.run_action(:install)
+      expect(subject).to be_updated_by_last_action
+    end
+  end
+
+  describe "uninstall exe without source" do
+    let(:pkg_name) { 'Mercurial 3.6.1 (64-bit)' }
+
+    it "uninstalls the app" do
+      subject.run_action(:remove)
+      expect(subject).to be_updated_by_last_action
+    end
+  end  
+end
\ No newline at end of file
diff --git a/spec/functional/resource/windows_service_spec.rb b/spec/functional/resource/windows_service_spec.rb
index 29d1fc4..9054542 100644
--- a/spec/functional/resource/windows_service_spec.rb
+++ b/spec/functional/resource/windows_service_spec.rb
@@ -18,7 +18,7 @@
 
 require 'spec_helper'
 
-describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_gem_only do
+describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_gem_only, :appveyor_only do
 
   include_context "using Win32::Service"
 
diff --git a/spec/functional/run_lock_spec.rb b/spec/functional/run_lock_spec.rb
index 0cb8635..78b3847 100644
--- a/spec/functional/run_lock_spec.rb
+++ b/spec/functional/run_lock_spec.rb
@@ -34,211 +34,232 @@ describe Chef::RunLock do
     let(:lockfile){ "#{random_temp_root}/this/long/path/does/not/exist/chef-client-running.pid" }
 
     # make sure to start with a clean slate.
-    before(:each){ FileUtils.rm_r(random_temp_root) if File.exist?(random_temp_root) }
-    after(:each){ FileUtils.rm_r(random_temp_root) }
+    before(:each){ log_event("rm -rf before"); FileUtils.rm_r(random_temp_root) if File.exist?(random_temp_root) }
+    after(:each){ log_event("rm -rf after"); FileUtils.rm_r(random_temp_root) if File.exist?(random_temp_root) }
 
-    def wait_on_lock
-      tries = 0
-      until File.exist?(lockfile)
-        raise "Lockfile never created, abandoning test" if tries > 10
-        tries += 1
-        sleep 0.1
-      end
+    def log_event(message, time=Time.now.strftime("%H:%M:%S.%L"))
+      events << [ message, time ]
     end
-
-    ##
-    # Side channel via a pipe allows child processes to send errors to the parent
-
-    # Don't lazy create the pipe or else we might not share it with subprocesses
-    let!(:error_pipe) do
-      r,w = IO.pipe
-      w.sync = true
-      [r,w]
+    def events
+      @events ||= []
     end
 
-    let(:error_read) { error_pipe[0] }
-    let(:error_write) { error_pipe[1] }
-
-    after do
-      error_read.close unless error_read.closed?
-      error_write.close unless error_write.closed?
+    WAIT_ON_LOCK_TIME = 1.0
+    def wait_on_lock
+      Timeout::timeout(WAIT_ON_LOCK_TIME) do
+        until File.exist?(lockfile)
+          sleep 0.1
+        end
+      end
+    rescue Timeout::Error
+      raise "Lockfile never created, abandoning test"
     end
 
-    # Send a RuntimeError from the child process to the parent process. Also
-    # prints error to $stdout, just in case something goes wrong with the error
-    # marshaling stuff.
-    def send_side_channel_error(message)
-      $stderr.puts(message)
-      $stderr.puts(caller)
-      e = RuntimeError.new(message)
-      error_write.print(Marshal.dump(e))
-    end
+    CLIENT_PROCESS_TIMEOUT = 10
+    BREATHING_ROOM = 1
 
-    # Read the error (if any) from the error channel. If a marhaled error is
-    # present, it is unmarshaled and raised (which will fail the test)
-    def raise_side_channel_error!
-      error_write.close
-      err = error_read.read
-      error_read.close
+    # ClientProcess is defined below
+    let!(:p1) { ClientProcess.new(self, 'p1') }
+    let!(:p2) { ClientProcess.new(self, 'p2') }
+    after(:each) do |example|
       begin
-        # ArgumentError from Marshal.load indicates no data, which we assume
-        # means no error in child process.
-        raise Marshal.load(err)
-      rescue ArgumentError
-        nil
+        p1.stop
+        p2.stop
+      rescue
+        example.exception = $!
+        raise
+      ensure
+        if example.exception
+          print_events
+        end
       end
     end
 
-    ##
-    # Interprocess synchronization via a pipe. This allows us to control the
-    # state of the processes competing over the lock without relying on sleep.
-
-    let!(:sync_pipe) do
-      r,w = IO.pipe
-      w.sync = true
-      [r,w]
-    end
-    let(:sync_read) { sync_pipe[0] }
-    let(:sync_write) { sync_pipe[1] }
-
-    after do
-      sync_read.close unless sync_read.closed?
-      sync_write.close unless sync_write.closed?
-    end
-
-    # Wait on synchronization signal. If not received within the timeout, an
-    # error is sent via the error channel, and the process exits.
-    def sync_wait
-      if IO.select([sync_read], nil, nil, 20).nil?
-        # timeout reading from the sync pipe.
-        send_side_channel_error("Error syncing processes in run lock test (timeout)")
-        exit!(1)
-      else
-        sync_read.getc
+    def print_events
+      # Consume any remaining events that went on the channel and print them all
+      p1.last_event
+      p2.last_event
+      events.each_with_index.sort_by { |(message, time), index| [ time, index ] }.each do |(message, time), index|
+        print "#{time} #{message}\n"
       end
     end
 
-    # Sends a character in the sync pipe, which wakes ("unlocks") another
-    # process that is waiting on the sync signal
-    def sync_send
-      sync_write.putc("!")
-      sync_write.flush
-    end
-
-    ##
-    # IPC to record test results in a pipe. Tests can read pipe contents to
-    # check that operations occur in the expected order.
-
-    let!(:results_pipe) do
-      r,w = IO.pipe
-      w.sync = true
-      [r,w]
-    end
-    let(:results_read) { results_pipe[0] }
-    let(:results_write) { results_pipe[1] }
-
-    after do
-      results_read.close unless results_read.closed?
-      results_write.close unless results_write.closed?
-    end
-
-    # writes the message to the results pipe for later checking.
-    # note that nothing accounts for the pipe filling and waiting forever on a
-    # read or write call, so don't put too much data in.
-    def record(message)
-      results_write.puts(message)
-      results_write.flush
-    end
-
-    def results
-      results_write.flush
-      results_write.close
-      message = results_read.read
-      results_read.close
-      message
-    end
-
-    ##
-    # Run lock is the system under test
-    let!(:run_lock) { Chef::RunLock.new(lockfile) }
-
-    it "creates the full path to the lockfile" do
-      expect { run_lock.acquire }.not_to raise_error
-      expect(File).to exist(lockfile)
-    end
-
-    it "sets FD_CLOEXEC on the lockfile", :supports_cloexec => true do
-      run_lock.acquire
-      expect(run_lock.runlock.fcntl(Fcntl::F_GETFD, 0) & Fcntl::FD_CLOEXEC).to eq(Fcntl::FD_CLOEXEC)
-    end
-
-    it "allows only one chef client run per lockfile" do
-      # First process, gets the lock and keeps it.
-      p1 = fork do
-        run_lock.acquire
-        record "p1 has lock"
-        # Wait until the other process is trying to get the lock:
-        sync_wait
-        # sleep a little bit to make process p2 wait on the lock
-        sleep 2
-        record "p1 releasing lock"
-        run_lock.release
-        exit!(0)
+    context "when the lockfile does not already exist" do
+      context "when a client creates the lockfile but has not yet acquired the lock" do
+        before { p1.run_to("created lock") }
+        shared_context "second client gets the lock" do
+          it "the lockfile is created" do
+            log_event("lockfile exists? #{File.exist?(lockfile)}")
+            expect(File.exist?(lockfile)).to be_truthy
+          end
+
+          it "the lockfile is not locked" do
+            run_lock = Chef::RunLock.new(lockfile)
+            begin
+              expect(run_lock.test).to be_truthy
+            ensure
+              run_lock.release
+            end
+          end
+
+          it "the lockfile is empty" do
+            expect(IO.read(lockfile)).to eq('')
+          end
+
+          context "and a second client gets the lock" do
+            before { p2.run_to("acquired lock") }
+            it "the first client does not get the lock until the second finishes" do
+              p1.run_to("acquired lock") do
+                p2.run_to_completion
+              end
+            end
+            it "and the first client tries to get the lock and the second is killed, the first client gets the lock immediately" do
+              p1.run_to("acquired lock") do
+                sleep BREATHING_ROOM
+                expect(p1.last_event).to match(/after (started|created lock)/)
+                p2.stop
+              end
+              p1.run_to_completion
+            end
+          end
+        end
+
+        context "and the second client has done nothing" do
+          include_context "second client gets the lock"
+        end
+
+        context "and the second client has created the lockfile but not yet acquired the lock" do
+          before { p2.run_to("created lock") }
+          include_context "second client gets the lock"
+        end
       end
 
-      # Wait until p1 creates the lockfile
-      wait_on_lock
-
-      p2 = fork do
-        # inform process p1 that we're trying to get the lock
-        sync_send
-        run_lock.acquire
-        record "p2 has lock"
-        run_lock.release
-        exit!(0)
+      context "when a client acquires the lock but has not yet saved the pid" do
+        before { p1.run_to("acquired lock") }
+
+        it "the lockfile is created" do
+          log_event("lockfile exists? #{File.exist?(lockfile)}")
+          expect(File.exist?(lockfile)).to be_truthy
+        end
+
+        it "the lockfile is locked" do
+          run_lock = Chef::RunLock.new(lockfile)
+          begin
+            expect(run_lock.test).to be_falsey
+          ensure
+            run_lock.release
+          end
+        end
+
+        it "sets FD_CLOEXEC on the lockfile", :supports_cloexec => true do
+          run_lock = File.open(lockfile)
+          expect(run_lock.fcntl(Fcntl::F_GETFD, 0) & Fcntl::FD_CLOEXEC).to eq(Fcntl::FD_CLOEXEC)
+        end
+
+        it "the lockfile is empty" do
+          expect(IO.read(lockfile)).to eq('')
+        end
+
+        it "and a second client tries to acquire the lock, it doesn't get the lock until *after* the first client exits" do
+          # Start p2 and tell it to move forward in the background
+          p2.run_to("acquired lock") do
+            # While p2 is trying to acquire, wait a bit and then let p1 complete
+            sleep(BREATHING_ROOM)
+            expect(p2.last_event).to match(/after (started|created lock)/)
+            p1.run_to_completion
+          end
+
+          p2.run_to_completion
+        end
+
+        it "and a second client tries to get the lock and the first is killed, the second client gets the lock immediately" do
+          p2.run_to("acquired lock") do
+            sleep BREATHING_ROOM
+            expect(p2.last_event).to match(/after (started|created lock)/)
+            p1.stop
+          end
+          p2.run_to_completion
+        end
       end
 
-      Process.waitpid2(p1)
-      Process.waitpid2(p2)
-
-      raise_side_channel_error!
-
-      expected=<<-E
-p1 has lock
-p1 releasing lock
-p2 has lock
-E
-      expect(results).to eq(expected)
-    end
-
-    it "clears the lock if the process dies unexpectedly" do
-      p1 = fork do
-        run_lock.acquire
-        record "p1 has lock"
-        sleep 60
-        record "p1 still has lock"
-        exit! 1
+      context "when a client acquires the lock and saves the pid" do
+        before { p1.run_to("saved pid") }
+
+        it "the lockfile is created" do
+          expect(File.exist?(lockfile)).to be_truthy
+        end
+
+        it "the lockfile is locked" do
+          run_lock = Chef::RunLock.new(lockfile)
+          begin
+            expect(run_lock.test).to be_falsey
+          ensure
+            run_lock.release
+          end
+        end
+
+        it "sets FD_CLOEXEC on the lockfile", :supports_cloexec => true do
+          run_lock = File.open(lockfile)
+          expect(run_lock.fcntl(Fcntl::F_GETFD, 0) & Fcntl::FD_CLOEXEC).to eq(Fcntl::FD_CLOEXEC)
+        end
+
+        it "the PID is in the lockfile" do
+          expect(IO.read(lockfile)).to eq p1.pid.to_s
+        end
+
+        it "and a second client tries to acquire the lock, it doesn't get the lock until *after* the first client exits" do
+          # Start p2 and tell it to move forward in the background
+          p2.run_to("acquired lock") do
+            # While p2 is trying to acquire, wait a bit and then let p1 complete
+            sleep(BREATHING_ROOM)
+            expect(p2.last_event).to match(/after (started|created lock)/)
+            p1.run_to_completion
+          end
+
+          p2.run_to_completion
+        end
+
+        it "when a second client tries to get the lock and the first is killed, the second client gets the lock immediately" do
+          p2.run_to("acquired lock") do
+            sleep BREATHING_ROOM
+            expect(p2.last_event).to match(/after (started|created lock)/)
+            p1.stop
+          end
+          p2.run_to_completion
+        end
       end
 
-      wait_on_lock
-      Process.kill(:KILL, p1)
-      Process.waitpid2(p1)
-
-      p2 = fork do
-        run_lock.acquire
-        record "p2 has lock"
-        run_lock.release
-        exit! 0
+      context "when a client acquires a lock and exits normally" do
+        before { p1.run_to_completion }
+
+        it "the lockfile remains" do
+          expect(File.exist?(lockfile)).to be_truthy
+        end
+
+        it "the lockfile is not locked" do
+          run_lock = Chef::RunLock.new(lockfile)
+          begin
+            expect(run_lock.test).to be_truthy
+          ensure
+            run_lock.release
+          end
+        end
+
+        it "the PID is in the lockfile" do
+          expect(IO.read(lockfile)).to eq p1.pid.to_s
+        end
+
+        it "and a second client tries to acquire the lock, it gets the lock immediately" do
+          p2.run_to_completion
+        end
       end
-
-      Process.waitpid2(p2)
-
-      expect(results).to match(/p2 has lock\Z/)
     end
 
     it "test returns true and acquires the lock" do
+      run_lock = Chef::RunLock.new(lockfile)
       p1 = fork do
         expect(run_lock.test).to eq(true)
+        run_lock.save_pid
         sleep 2
         exit! 1
       end
@@ -255,8 +276,10 @@ E
     end
 
     it "test returns without waiting when the lock is acquired" do
+      run_lock = Chef::RunLock.new(lockfile)
       p1 = fork do
         run_lock.acquire
+        run_lock.save_pid
         sleep 2
         exit! 1
       end
@@ -267,20 +290,176 @@ E
       Process.waitpid2(p1)
     end
 
-    it "doesn't truncate the lock file so that contents can be read" do
-      p1 = fork do
-        run_lock.acquire
-        run_lock.save_pid
-        sleep 2
-        exit! 1
+  end
+
+  #
+  # Runs a process in the background that will:
+  #
+  # 1. start up (`started` event)
+  # 2. acquire the runlock file (`acquired lock` event)
+  # 3. save the pid to the lockfile (`saved pid` event)
+  # 4. exit
+  #
+  # You control exactly how far the client process goes with the `run_to`
+  # method: it will stop at any given spot so you can test for race conditions.
+  #
+  # It uses a pair of pipes to communicate with the process. The tests will
+  # send an event name over to the process, which gives the process permission
+  # to run until it reaches that event (at which point it waits for another event
+  # name). The process sends the name of each event it reaches back to the tests.
+  #
+  class ClientProcess
+    def initialize(example, name)
+      @example = example
+      @name = name
+      @read_from_process, @write_to_tests = IO.pipe
+      @read_from_tests, @write_to_process = IO.pipe
+    end
+
+    attr_reader :example
+    attr_reader :name
+    attr_reader :pid
+
+    def last_event
+      while true
+        line = readline_nonblock(read_from_process)
+        break if line.nil?
+        event, time = line.split("@")
+        example.log_event("#{name}.last_event got #{event}")
+        example.log_event("[#{name}] #{event}", time.strip)
+        @last_event = event
       end
+      @last_event
+    end
 
-      wait_on_lock
-      sleep 0.5 # Possible race condition on Solaris which pid is observed as 0
-      expect(File.read(lockfile)).to eq(p1.to_s)
+    def run_to(to_event, &background_block)
+      example.log_event("#{name}.run_to(#{to_event.inspect})")
 
-      Process.waitpid2(p1)
+      # Start the process if it's not started
+      start if !pid
+
+      # Tell the process what to stop at (also means it can go)
+      write_to_process.print "#{to_event}\n"
+
+      # Run the background block
+      background_block.call if background_block
+
+      # Wait until it gets there
+      Timeout::timeout(CLIENT_PROCESS_TIMEOUT) do
+        until @last_event == "after #{to_event}"
+          got_event, time = read_from_process.gets.split("@")
+          example.log_event("#{name}.last_event got #{got_event}")
+          example.log_event("[#{name}] #{got_event}", time.strip)
+          @last_event = got_event
+        end
+      end
+
+      example.log_event("#{name}.run_to(#{to_event.inspect}) finished")
     end
 
+    def run_to_completion
+      example.log_event("#{name}.run_to_completion")
+      # Start the process if it's not started
+      start if !pid
+
+      # Tell the process to stop at nothing (no blocking)
+      @write_to_process.print "nothing\n"
+
+      # Wait for the process to exit
+      wait_for_exit
+      example.log_event("#{name}.run_to_completion finished")
+    end
+
+    def wait_for_exit
+      example.log_event("#{name}.wait_for_exit (pid #{pid})")
+      Timeout::timeout(CLIENT_PROCESS_TIMEOUT) do
+        Process.wait(pid) if pid
+      end
+      example.log_event("#{name}.wait_for_exit finished (pid #{pid})")
+    end
+
+    def stop
+      if pid
+        example.log_event("#{name}.stop (pid #{pid})")
+        begin
+          # Send it the kill signal over and over until it dies
+          Timeout::timeout(CLIENT_PROCESS_TIMEOUT) do
+            Process.kill(:KILL, pid)
+            while !Process.waitpid2(pid, Process::WNOHANG)
+              sleep(0.05)
+            end
+          end
+          example.log_event("#{name}.stop finished (stopped pid #{pid})")
+        # Process not found is perfectly fine when we're trying to kill a process :)
+        rescue Errno::ESRCH
+          example.log_event("#{name}.stop finished (pid #{pid} wasn't running)")
+        end
+      end
+    end
+
+    def fire_event(event)
+      # Let the caller know what event we've reached
+      write_to_tests.print("after #{event}@#{Time.now.strftime("%H:%M:%S.%L")}\n")
+
+      # Block until the client tells us where to stop
+      if !@run_to_event || event == @run_to_event
+        write_to_tests.print("waiting for instructions after #{event}@#{Time.now.strftime("%H:%M:%S.%L")}\n")
+        @run_to_event = read_from_tests.gets.strip
+        write_to_tests.print("told to run to #{@run_to_event} after #{event}@#{Time.now.strftime("%H:%M:%S.%L")}\n")
+      elsif @run_to_event
+        write_to_tests.print("continuing until #{@run_to_event} after #{event}@#{Time.now.strftime("%H:%M:%S.%L")}\n")
+      end
+    end
+
+    private
+
+    attr_reader :read_from_process
+    attr_reader :write_to_tests
+    attr_reader :read_from_tests
+    attr_reader :write_to_process
+
+    class TestRunLock < Chef::RunLock
+      attr_accessor :client_process
+      def create_lock
+        super
+        client_process.fire_event("created lock")
+      end
+    end
+
+    def start
+      example.log_event("#{name}.start")
+      @pid = fork do
+        begin
+          Timeout::timeout(CLIENT_PROCESS_TIMEOUT) do
+            run_lock = TestRunLock.new(example.lockfile)
+            run_lock.client_process = self
+            fire_event("started")
+            run_lock.acquire
+            fire_event("acquired lock")
+            run_lock.save_pid
+            fire_event("saved pid")
+            exit!(0)
+          end
+        rescue
+          fire_event($!.message.lines.join(" // "))
+          raise
+        end
+      end
+      example.log_event("#{name}.start forked (pid #{pid})")
+    end
+
+    def readline_nonblock(fd)
+      buffer = ""
+      buffer << fd.read_nonblock(1) while buffer[-1] != "\n"
+
+      buffer
+    #rescue IO::EAGAINUnreadable
+    rescue IO::WaitReadable
+      unless buffer == ""
+        sleep 0.1
+        retry
+      end
+      nil
+    end
   end
 end
diff --git a/spec/functional/shell_spec.rb b/spec/functional/shell_spec.rb
index fa9de77..a753948 100644
--- a/spec/functional/shell_spec.rb
+++ b/spec/functional/shell_spec.rb
@@ -29,6 +29,8 @@ describe Shell do
   describe "smoke tests", :unix_only => true do
     include Chef::Mixin::Command::Unix
 
+    TIMEOUT=300
+
     def read_until(io, expected_value)
       start = Time.new
       buffer = ""
@@ -38,15 +40,30 @@ describe Shell do
         rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EIO, EOFError
           sleep 0.01
         end
-        if Time.new - start > 30
-          STDERR.puts "did not read expected value `#{expected_value}' within 15s"
-          STDERR.puts "Buffer so far: `#{buffer}'"
-          break
+        if Time.new - start > TIMEOUT
+          raise "did not read expected value `#{expected_value}' within #{TIMEOUT}s\n" +
+                "Buffer so far: `#{buffer}'"
         end
       end
       buffer
     end
 
+    def flush_output(io)
+      start = Time.new
+      loop do
+        begin
+          io.read_nonblock(1)
+        rescue Errno::EWOULDBLOCK, Errno::EAGAIN
+          sleep 0.01
+        rescue EOFError, Errno::EIO
+          break
+        end
+        if Time.new - start > TIMEOUT
+          raise "timed out after #{TIMEOUT}s waiting for output to end"
+        end
+      end
+    end
+
     def wait_or_die(pid)
       start = Time.new
 
@@ -67,12 +84,12 @@ describe Shell do
         path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__)
         output = ''
         status = popen4("#{path_to_chef_shell} -c #{config} #{options}", :waitlast => true) do |pid, stdin, stdout, stderr|
-          read_until(stdout, "chef >")
+          read_until(stdout, "chef (#{Chef::VERSION})>")
           yield stdout, stdin if block_given?
           stdin.write("'done'\n")
           output = read_until(stdout, '=> "done"')
           stdin.print("exit\n")
-          read_until(stdout, "\n")
+          flush_output(stdout)
         end
 
         [output, status.exitstatus]
@@ -84,14 +101,12 @@ describe Shell do
           config = File.expand_path("shef-config.rb", CHEF_SPEC_DATA)
           path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__)
           reader, writer, pid = PTY.spawn("#{path_to_chef_shell} -c #{config} #{options}")
-          read_until(reader, "chef >")
+          read_until(reader, "chef (#{Chef::VERSION})>")
           yield reader, writer if block_given?
           writer.puts('"done"')
           output = read_until(reader, '=> "done"')
           writer.print("exit\n")
-          read_until(reader, "exit")
-          read_until(reader, "\n")
-          read_until(reader, "\n")
+          flush_output(reader)
           writer.close
 
           exitstatus = wait_or_die(pid)
diff --git a/spec/functional/win32/registry_helper_spec.rb b/spec/functional/win32/registry_spec.rb
similarity index 96%
rename from spec/functional/win32/registry_helper_spec.rb
rename to spec/functional/win32/registry_spec.rb
index 7b070e6..dcfc49e 100644
--- a/spec/functional/win32/registry_helper_spec.rb
+++ b/spec/functional/win32/registry_spec.rb
@@ -20,25 +20,6 @@
 require 'spec_helper'
 require 'chef/win32/registry'
 
-describe Chef::Resource::RegistryKey, :unix_only do
-  before(:all) do
-    events = Chef::EventDispatch::Dispatcher.new
-    node = Chef::Node.new
-    ohai = Ohai::System.new
-    ohai.all_plugins
-    node.consume_external_attrs(ohai.data,{})
-    run_context = Chef::RunContext.new(node, {}, events)
-    @resource = Chef::Resource::RegistryKey.new("HKCU\\Software", run_context)
-  end
-  context "when load_current_resource is run on a non-windows node" do
-    it "throws an exception because you don't have a windows registry (derp)" do
-      @resource.key("HKCU\\Software\\Opscode")
-      @resource.values([{:name=>"Color", :type=>:string, :data=>"Orange"}])
-      expect{@resource.run_action(:create)}.to raise_error(Chef::Exceptions::Win32NotWindows)
-    end
-  end
-end
-
 describe 'Chef::Win32::Registry', :windows_only do
 
   before(:all) do
@@ -130,6 +111,9 @@ describe 'Chef::Win32::Registry', :windows_only do
     it "returns true if the value exists" do
       expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true)
     end
+    it "returns true if the value exists with a case mismatch on the value name" do
+      expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true)
+    end
     it "returns false if the value does not exist" do
       expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})).to eq(false)
     end
@@ -145,6 +129,9 @@ describe 'Chef::Win32::Registry', :windows_only do
     it "returns true if the value exists" do
       expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true)
     end
+    it "returns true if the value exists with a case mismatch on the value name" do
+      expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true)
+    end
     it "throws an exception if the value does not exist" do
       expect {@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})}.to raise_error(Chef::Exceptions::Win32RegValueMissing)
     end
@@ -160,6 +147,9 @@ describe 'Chef::Win32::Registry', :windows_only do
     it "returns true if all the data matches" do
       expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true)
     end
+    it "returns true if all the data matches with a case mismatch on the data name" do
+      expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true)
+    end
     it "returns false if the name does not exist" do
       expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(false)
     end
@@ -181,6 +171,9 @@ describe 'Chef::Win32::Registry', :windows_only do
     it "returns true if all the data matches" do
       expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true)
     end
+    it "returns true if all the data matches with a case mismatch on the data name" do
+      expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true)
+    end
     it "throws an exception if the name does not exist" do
       expect {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegDataMissing)
     end
@@ -544,11 +537,11 @@ describe 'Chef::Win32::Registry', :windows_only do
       end
 
       after(:all) do
-        ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg|
-          reg.delete_key("Trunk", true)
+        ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg|
+          reg.delete_key("Root", true)
         end
-        ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg|
-          reg.delete_key("Trunk", true)
+        ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg|
+          reg.delete_key("Root", true)
         end
       end
 
diff --git a/spec/functional/win32/service_manager_spec.rb b/spec/functional/win32/service_manager_spec.rb
index d2474de..a1ce361 100644
--- a/spec/functional/win32/service_manager_spec.rb
+++ b/spec/functional/win32/service_manager_spec.rb
@@ -33,7 +33,7 @@ end
 # directories.
 #
 
-describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only do
+describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only, :appveyor_only do
 
   include_context "using Win32::Service"
 
@@ -43,7 +43,7 @@ describe "Chef::Application::WindowsServiceManager", :windows_only, :system_wind
     end
 
     it "throws an error with required missing options" do
-      test_service.each do |key,value|
+      [:service_name, :service_display_name, :service_description, :service_file_path].each do |key|
         service_def = test_service.dup
         service_def.delete(key)
 
diff --git a/spec/functional/win32/sid_spec.rb b/spec/functional/win32/sid_spec.rb
new file mode 100644
index 0000000..1f5f661
--- /dev/null
+++ b/spec/functional/win32/sid_spec.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Dan Bjorge (<dbjorge at gmail.com>)
+# Copyright:: Copyright (c) 2015 Dan Bjorge
+# 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 'spec_helper'
+if Chef::Platform.windows?
+  require 'chef/win32/security'
+end
+
+describe 'Chef::ReservedNames::Win32::SID', :windows_only do
+  if Chef::Platform.windows?
+    SID ||= Chef::ReservedNames::Win32::Security::SID
+  end
+
+  it 'should resolve default_security_object_group as a sane user group', :windows_not_domain_joined_only do
+    # Domain accounts: domain-specific Domain Users SID
+    # Microsoft Accounts: SID.current_user
+    # Else: SID.None
+    expect(SID.default_security_object_group).to eq(SID.None).or eq(SID.current_user)
+  end
+
+  context 'running as an elevated administrator user' do
+    it 'should resolve default_security_object_owner as the Administrators group' do
+      expect(SID.default_security_object_owner).to eq(SID.Administrators)
+    end
+  end
+
+  context 'running as a non-elevated administrator user' do
+    it 'should resolve default_security_object_owner as the current user' do
+      skip 'requires user support in mixlib-shellout, see security_spec.rb'
+      expect(SID.default_security_object_owner).to eq(SID.Administrators)
+    end
+  end
+
+  context 'running as a non-elevated, non-administrator user' do
+    it 'should resolve default_security_object_owner as the current user' do
+      skip 'requires user support in mixlib-shellout, see security_spec.rb'
+      expect(SID.default_security_object_owner).to eq(SID.current_user)
+    end
+  end
+end
diff --git a/spec/functional/win32/version_info_spec.rb b/spec/functional/win32/version_info_spec.rb
new file mode 100644
index 0000000..c7d41f9
--- /dev/null
+++ b/spec/functional/win32/version_info_spec.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Matt Wrock (<matt at mattwrock.com>)
+# Copyright:: Copyright 2015 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 'spec_helper'
+if Chef::Platform.windows?
+  require 'chef/win32/file/version_info'
+end
+
+describe "Chef::ReservedNames::Win32::File::VersionInfo", :windows_only do
+  require 'wmi-lite/wmi'
+  let(:file_path) { ENV['ComSpec'] }
+  let(:os_version) do
+    wmi = WmiLite::Wmi.new
+    os_info = wmi.first_of('Win32_OperatingSystem')
+    os_info['version']
+  end
+
+  subject { Chef::ReservedNames::Win32::File::VersionInfo.new(file_path) }
+
+  it "file version has the same version as windows" do
+    expect(subject.FileVersion).to start_with(os_version)
+  end
+
+  it "product version has the same version as windows" do
+    expect(subject.ProductVersion).to start_with(os_version)
+  end
+
+  it "company is microsoft" do
+    expect(subject.CompanyName).to eq("Microsoft Corporation")
+  end
+
+  it "file description is command processor" do
+    expect(subject.FileDescription).to eq("Windows Command Processor")
+  end
+end
diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb
index b5c5e12..5e72a94 100644
--- a/spec/integration/client/client_spec.rb
+++ b/spec/integration/client/client_spec.rb
@@ -3,34 +3,35 @@ require 'chef/mixin/shell_out'
 require 'tiny_server'
 require 'tmpdir'
 
-def recipes_filename
-  File.join(CHEF_SPEC_DATA, 'recipes.tgz')
-end
 
-def start_tiny_server(server_opts={})
-  recipes_size = File::Stat.new(recipes_filename).size
-  @server = TinyServer::Manager.new(server_opts)
-  @server.start
-    @api = TinyServer::API.instance
-  @api.clear
-  #
-  # trivial endpoints
-  #
-  # just a normal file
-  # (expected_content should be uncompressed)
-  @api.get("/recipes.tgz", 200) {
-    File.open(recipes_filename, "rb") do |f|
-      f.read
-    end
-  }
-end
+describe "chef-client" do
 
-def stop_tiny_server
-  @server.stop
-  @server = @api = nil
-end
+  def recipes_filename
+    File.join(CHEF_SPEC_DATA, 'recipes.tgz')
+  end
+
+  def start_tiny_server(server_opts={})
+    @server = TinyServer::Manager.new(server_opts)
+    @server.start
+      @api = TinyServer::API.instance
+    @api.clear
+    #
+    # trivial endpoints
+    #
+    # just a normal file
+    # (expected_content should be uncompressed)
+    @api.get("/recipes.tgz", 200) {
+      File.open(recipes_filename, "rb") do |f|
+        f.read
+      end
+    }
+  end
+
+  def stop_tiny_server
+    @server.stop
+    @server = @api = nil
+  end
 
-describe "chef-client" do
   include IntegrationSupport
   include Chef::Mixin::ShellOut
 
@@ -47,6 +48,8 @@ describe "chef-client" do
   # cf. CHEF-4914
   let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" }
 
+  let(:critical_env_vars) { %w(PATH RUBYOPT BUNDLE_GEMFILE GEM_PATH).map {|o| "#{o}=#{ENV[o]}"} .join(' ') }
+
   when_the_repository "has a cookbook with a no-op recipe" do
     before { file 'cookbooks/x/recipes/default.rb', '' }
 
@@ -56,8 +59,23 @@ local_mode true
 cookbook_path "#{path_to('cookbooks')}"
 EOM
 
-      result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
-      result.error!
+      shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+    end
+
+    it "should complete successfully with no other environment variables", :skip => (Chef::Platform.windows?) do
+      file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+      begin
+        result = shell_out("env -i #{critical_env_vars} #{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+        result.error!
+      rescue
+        Chef::Log.info "Bare invocation will have the following load-path."
+        Chef::Log.info shell_out!("env -i #{critical_env_vars} ruby -e 'puts $:'").stdout
+        raise
+      end
     end
 
     it "should complete successfully with --no-listen" do
@@ -70,6 +88,11 @@ EOM
       result.error!
     end
 
+    it "should be able to node.save with bad utf8 characters in the node data" do
+      file "cookbooks/x/attributes/default.rb", 'default["badutf8"] = "Elan Ruusam\xE4e"'
+      result = shell_out("#{chef_client} -z -r 'x::default' --disable-config", :cwd => path_to(''))
+      result.error!
+    end
 
     context 'and no config file' do
       it 'should complete with success when cwd is just above cookbooks and paths are not specified' do
@@ -278,6 +301,137 @@ EOM
       result.error!
     end
 
+    it "should complete with success when using --profile-ruby and output a profile file" do
+      file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+      result = shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' -z --profile-ruby", :cwd => chef_dir)
+      expect(File.exist?(path_to("config/local-mode-cache/cache/graph_profile.out"))).to be true
+    end
+
+    it "doesn't produce a profile when --profile-ruby is not present" do
+      file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+      result = shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' -z", :cwd => chef_dir)
+      expect(File.exist?(path_to("config/local-mode-cache/cache/graph_profile.out"))).to be false
+    end
+  end
+
+  when_the_repository "has a cookbook that should fail chef_version checks" do
+    before do
+      file 'cookbooks/x/recipes/default.rb', ''
+      file 'cookbooks/x/metadata.rb', <<EOM
+name 'x'
+version '0.0.1'
+chef_version '~> 999.99'
+EOM
+      file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+    end
+    it "should fail the chef client run" do
+      command = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork", :cwd => chef_dir)
+      expect(command.exitstatus).to eql(1)
+      expect(command.stdout).to match(/Chef::Exceptions::CookbookChefVersionMismatch/)
+    end
+  end
+
+  when_the_repository "has a cookbook that uses cheffish resources" do
+    before do
+      file 'cookbooks/x/recipes/default.rb', <<-EOM
+        raise "Cheffish was loaded before we used any cheffish things!" if defined?(Cheffish::VERSION)
+        ran_block = false
+        got_server = with_chef_server 'https://blah.com' do
+          ran_block = true
+          run_context.cheffish.current_chef_server
+        end
+        raise "with_chef_server block was not run!" if !ran_block
+        raise "Cheffish was not loaded when we did cheffish things!" if !defined?(Cheffish::VERSION)
+        raise "current_chef_server did not return its value!" if got_server[:chef_server_url] != 'https://blah.com'
+      EOM
+      file 'config/client.rb', <<-EOM
+        local_mode true
+        cookbook_path "#{path_to('cookbooks')}"
+      EOM
+    end
+
+    it "the cheffish DSL is loaded lazily" do
+      command = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork", :cwd => chef_dir)
+      expect(command.exitstatus).to eql(0)
+    end
+  end
+
+  when_the_repository "has a cookbook that uses chef-provisioning resources" do
+    before do
+      file 'cookbooks/x/recipes/default.rb', <<-EOM
+        with_driver 'blah'
+      EOM
+      file 'config/client.rb', <<-EOM
+        local_mode true
+        cookbook_path "#{path_to('cookbooks')}"
+      EOM
+    end
+
+    it "the cheffish DSL tries to load but fails (because chef-provisioning is not there)" do
+      command = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork", :cwd => chef_dir)
+      expect(command.exitstatus).to eql(1)
+      expect(command.stdout).to match(/cannot load such file -- chef\/provisioning/)
+    end
+  end
+
+  when_the_repository "has a cookbook that generates deprecation warnings" do
+    before do
+      file 'cookbooks/x/recipes/default.rb', <<-EOM
+        class ::MyResource < Chef::Resource
+          use_automatic_resource_name
+          property :x, default: []
+          property :y, default: {}
+        end
+
+        my_resource 'blah' do
+          1.upto(10) do
+            x nil
+          end
+          x nil
+        end
+      EOM
+    end
+
+    def match_indices(regex, str)
+      result = []
+      pos = 0
+      while match = regex.match(str, pos)
+        result << match.begin(0)
+        pos = match.end(0) + 1
+      end
+      result
+    end
+
+    it "should output each deprecation warning only once, at the end of the run" do
+      file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+# Mimick what happens when you are on the console
+formatters << :doc
+log_level :warn
+EOM
+
+      ENV.delete('CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS')
+
+      result = shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+      expect(result.error?).to be_falsey
+
+      # Search to the end of the client run in the output
+      run_complete = result.stdout.index("Running handlers complete")
+      expect(run_complete).to be >= 0
+
+      # Make sure there is exactly one result for each, and that it occurs *after* the complete message.
+      expect(match_indices(/nil currently does not overwrite the value of/, result.stdout)).to match([ be > run_complete ])
+    end
   end
 
   when_the_repository "has a cookbook with only an audit recipe" do
@@ -319,7 +473,8 @@ end
     end
   end
 
-  context "when using recipe-url" do
+  # Fails on appveyor, but works locally on windows and on windows hosts in Ci.
+  context "when using recipe-url", :skip_appveyor do
     before(:all) do
       start_tiny_server
     end
@@ -340,7 +495,7 @@ EOM
 
     it 'should fail when passed --recipe-url and not passed -z' do
       result = shell_out("#{chef_client} --recipe-url=http://localhost:9000/recipes.tgz", :cwd => tmp_dir)
-      expect(result.exitstatus).to eq(1)
+      expect(result.exitstatus).not_to eq(0)
     end
   end
 end
diff --git a/spec/integration/knife/chef_repo_path_spec.rb b/spec/integration/knife/chef_repo_path_spec.rb
index 874b339..908657e 100644
--- a/spec/integration/knife/chef_repo_path_spec.rb
+++ b/spec/integration/knife/chef_repo_path_spec.rb
@@ -24,6 +24,8 @@ describe 'chef_repo_path tests', :workstation do
   include IntegrationSupport
   include KnifeSupport
 
+  let(:error_rel_path_outside_repo) { /^ERROR: Attempt to use relative path '' when current directory is outside the repository path/ }
+
   # TODO alternate repo_path / *_path
   context 'alternate *_path' do
     when_the_repository 'has clients and clients2, cookbooks and cookbooks2, etc.' do
@@ -109,14 +111,14 @@ EOM
         context 'when cwd is at the top level' do
           before { cwd '.' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
         context 'when cwd is inside the data_bags directory' do
           before { cwd 'data_bags' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
@@ -192,14 +194,14 @@ EOM
         context 'when cwd is inside the data_bags directory' do
           before { cwd 'data_bags' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
         context 'when cwd is inside chef_repo2' do
           before { cwd 'chef_repo2' }
           it 'knife list -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
@@ -225,14 +227,14 @@ EOM
         context 'when cwd is at the top level' do
           before { cwd '.' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
         context 'when cwd is inside the data_bags directory' do
           before { cwd 'data_bags' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
@@ -445,7 +447,7 @@ EOM
         context 'when cwd is at the top level' do
           before { cwd '.' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
@@ -621,14 +623,14 @@ EOM
         context 'when cwd is at the top level' do
           before { cwd '.' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
         context 'when cwd is inside the data_bags directory' do
           before { cwd 'data_bags' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
@@ -782,7 +784,7 @@ EOM
         context 'when cwd is at the top level' do
           before { cwd '.' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
 
@@ -823,7 +825,7 @@ EOM
         context 'when cwd is inside chef_repo2/data_bags' do
           before { cwd 'chef_repo2/data_bags' }
           it 'knife list --local -Rfp fails' do
-            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+            knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
           end
         end
       end
diff --git a/spec/integration/knife/deps_spec.rb b/spec/integration/knife/deps_spec.rb
index 3120db4..b7333ce 100644
--- a/spec/integration/knife/deps_spec.rb
+++ b/spec/integration/knife/deps_spec.rb
@@ -216,22 +216,16 @@ depends "self"'
         end
 
         it 'knife deps prints each once' do
-          knife('deps /cookbooks/foo /cookbooks/self').should_succeed <<EOM
-/cookbooks/baz
-/cookbooks/bar
-/cookbooks/foo
-/cookbooks/self
-EOM
+          knife('deps /cookbooks/foo /cookbooks/self').should_succeed(
+            stdout: "/cookbooks/baz\n/cookbooks/bar\n/cookbooks/foo\n/cookbooks/self\n",
+            stderr: "WARN: Ignoring self-dependency in cookbook self, please remove it (in the future this will be fatal).\n"
+          )
         end
         it 'knife deps --tree prints each once' do
-          knife('deps --tree /cookbooks/foo /cookbooks/self').should_succeed <<EOM
-/cookbooks/foo
-  /cookbooks/bar
-    /cookbooks/baz
-      /cookbooks/foo
-/cookbooks/self
-  /cookbooks/self
-EOM
+          knife('deps --tree /cookbooks/foo /cookbooks/self').should_succeed(
+            stdout: "/cookbooks/foo\n  /cookbooks/bar\n    /cookbooks/baz\n      /cookbooks/foo\n/cookbooks/self\n",
+            stderr: "WARN: Ignoring self-dependency in cookbook self, please remove it (in the future this will be fatal).\n"
+          )
         end
       end
       when_the_repository 'has roles with circular dependencies' do
diff --git a/spec/integration/knife/download_spec.rb b/spec/integration/knife/download_spec.rb
index c87e6fe..b8a1906 100644
--- a/spec/integration/knife/download_spec.rb
+++ b/spec/integration/knife/download_spec.rb
@@ -1103,6 +1103,15 @@ EOM
     before :each do
       Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, '/organizations/foo')
     end
+    when_the_repository 'has existing top level files' do
+      before do
+        file 'invitations.json', {}
+      end
+
+      it "can still download top level files" do
+        knife('download /invitations.json').should_succeed
+      end
+    end
 
     when_the_repository 'is empty' do
       it 'knife download / downloads everything' do
@@ -1113,11 +1122,13 @@ Created /acls/clients/foo-validator.json
 Created /acls/containers
 Created /acls/containers/clients.json
 Created /acls/containers/containers.json
+Created /acls/containers/cookbook_artifacts.json
 Created /acls/containers/cookbooks.json
 Created /acls/containers/data.json
 Created /acls/containers/environments.json
 Created /acls/containers/groups.json
 Created /acls/containers/nodes.json
+Created /acls/containers/policies.json
 Created /acls/containers/roles.json
 Created /acls/containers/sandboxes.json
 Created /acls/containers/x.json
@@ -1139,11 +1150,13 @@ Created /clients/foo-validator.json
 Created /containers
 Created /containers/clients.json
 Created /containers/containers.json
+Created /containers/cookbook_artifacts.json
 Created /containers/cookbooks.json
 Created /containers/data.json
 Created /containers/environments.json
 Created /containers/groups.json
 Created /containers/nodes.json
+Created /containers/policies.json
 Created /containers/roles.json
 Created /containers/sandboxes.json
 Created /containers/x.json
diff --git a/spec/integration/knife/list_spec.rb b/spec/integration/knife/list_spec.rb
index 911b56e..b289642 100644
--- a/spec/integration/knife/list_spec.rb
+++ b/spec/integration/knife/list_spec.rb
@@ -702,11 +702,13 @@ foo-validator.json
 /acls/containers:
 clients.json
 containers.json
+cookbook_artifacts.json
 cookbooks.json
 data.json
 environments.json
 groups.json
 nodes.json
+policies.json
 roles.json
 sandboxes.json
 
@@ -733,11 +735,13 @@ foo-validator.json
 /containers:
 clients.json
 containers.json
+cookbook_artifacts.json
 cookbooks.json
 data.json
 environments.json
 groups.json
 nodes.json
+policies.json
 roles.json
 sandboxes.json
 
@@ -804,11 +808,13 @@ foo-validator.json
 /acls/containers:
 clients.json
 containers.json
+cookbook_artifacts.json
 cookbooks.json
 data.json
 environments.json
 groups.json
 nodes.json
+policies.json
 roles.json
 sandboxes.json
 
@@ -835,11 +841,13 @@ foo-validator.json
 /containers:
 clients.json
 containers.json
+cookbook_artifacts.json
 cookbooks.json
 data.json
 environments.json
 groups.json
 nodes.json
+policies.json
 roles.json
 sandboxes.json
 
diff --git a/spec/integration/knife/upload_spec.rb b/spec/integration/knife/upload_spec.rb
index cef4f54..d72cbb8 100644
--- a/spec/integration/knife/upload_spec.rb
+++ b/spec/integration/knife/upload_spec.rb
@@ -1,6 +1,6 @@
 #
 # Author:: John Keiser (<jkeiser at opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# Copyright:: Copyright (c) 2013-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -154,6 +154,24 @@ EOM
           end
         end
 
+        context 'when cookbook metadata has a self-dependency' do
+          before do
+            file 'cookbooks/x/metadata.rb', "name 'x'; version '1.0.0'; depends 'x'"
+          end
+
+          it "should warn", :chef_lt_13_only do
+            knife('upload /cookbooks').should_succeed(
+              stdout: "Updated /cookbooks/x\n",
+              stderr: "WARN: Ignoring self-dependency in cookbook x, please remove it (in the future this will be fatal).\n"
+            )
+            knife('diff --name-status /').should_succeed ''
+          end
+          it "should fail in Chef 13", :chef_gte_13_only do
+            knife('upload /cookbooks').should_fail ''
+            # FIXME: include the error message here
+          end
+        end
+
         context 'as well as one extra copy of each thing' do
           before do
             file 'clients/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
@@ -180,7 +198,7 @@ Created /nodes/y.json
 Created /roles/y.json
 Created /users/y.json
 EOM
-            knife('diff --name-status /').should_succeed ''
+            knife('diff /').should_succeed ''
           end
 
           it 'knife upload --no-diff adds the new files' do
@@ -681,6 +699,19 @@ EOH
         end
       end
     end
+    when_the_chef_server "is empty" do
+      when_the_repository 'has a cookbook with an invalid chef_version constraint in it' do
+        before do
+          file 'cookbooks/x/metadata.rb', cb_metadata('x', '1.0.0', "\nchef_version '~> 999.0'")
+        end
+        it 'knife upload succeeds' do
+          knife('upload /cookbooks/x').should_succeed <<EOM
+Created /cookbooks/x
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+    end
   end # without versioned cookbooks
 
   with_versioned_cookbooks do
@@ -1201,6 +1232,20 @@ EOM
         end
       end
     end
+
+    when_the_chef_server "is empty" do
+      when_the_repository 'has a cookbook with an invalid chef_version constraint in it' do
+        before do
+          file 'cookbooks/x-1.0.0/metadata.rb', cb_metadata('x', '1.0.0', "\nchef_version '~> 999.0'")
+        end
+        it 'knife upload succeeds' do
+          knife('upload /cookbooks/x-1.0.0').should_succeed <<EOM
+Created /cookbooks/x-1.0.0
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+    end
   end # with versioned cookbooks
 
   when_the_chef_server 'has a user' do
diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb
index b4c4e6c..bd5eaf6 100644
--- a/spec/integration/recipes/lwrp_inline_resources_spec.rb
+++ b/spec/integration/recipes/lwrp_inline_resources_spec.rb
@@ -5,7 +5,7 @@ describe "LWRPs with inline resources" do
   include IntegrationSupport
   include Chef::Mixin::ShellOut
 
-  let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") }
+  let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) }
 
   # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
   # following constraints are satisfied:
@@ -18,35 +18,105 @@ describe "LWRPs with inline resources" do
   # cf. CHEF-4914
   let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" }
 
+  context "with a use_inline_resources provider with 'def action_a' instead of action :a" do
+    class LwrpInlineResourcesTest < Chef::Resource::LWRPBase
+      resource_name :lwrp_inline_resources_test
+      actions :a, :nothing
+      default_action :a
+      property :ran_a
+      class Provider < Chef::Provider::LWRPBase
+        provides :lwrp_inline_resources_test
+        use_inline_resources
+        def action_a
+          r = new_resource
+          ruby_block 'run a' do
+            block { r.ran_a "ran a" }
+          end
+        end
+      end
+    end
+
+    it "this is totally a bug, but for backcompat purposes, it adds the resources to the main resource collection and does not get marked updated" do
+      r = nil
+      expect_recipe {
+        r = lwrp_inline_resources_test 'hi'
+      }.to have_updated('ruby_block[run a]', :run)
+      expect(r.ran_a).to eq "ran a"
+    end
+  end
+
+  context "with an inline_resources provider with two actions, one calling the other" do
+    class LwrpInlineResourcesTest2 < Chef::Resource::LWRPBase
+      resource_name :lwrp_inline_resources_test2
+      actions :a, :b, :nothing
+      default_action :b
+      property :ran_a
+      property :ran_b
+      class Provider < Chef::Provider::LWRPBase
+        provides :lwrp_inline_resources_test2
+        use_inline_resources
+
+        action :a do
+          r = new_resource
+          ruby_block 'run a' do
+            block { r.ran_a "ran a" }
+          end
+        end
+
+        action :b do
+          action_a
+          r = new_resource
+          # Grab ran_a right now, before we converge
+          ran_a = r.ran_a
+          ruby_block 'run b' do
+            block { r.ran_b "ran b: ran_a value was #{ran_a.inspect}" }
+          end
+        end
+      end
+    end
+
+    it "resources declared in b are executed immediately inline" do
+      r = nil
+      expect_recipe {
+        r = lwrp_inline_resources_test2 'hi' do
+          action :b
+        end
+      }.to have_updated('lwrp_inline_resources_test2[hi]', :b).
+       and have_updated('ruby_block[run a]', :run).
+       and have_updated('ruby_block[run b]', :run)
+      expect(r.ran_b).to eq "ran b: ran_a value was \"ran a\""
+    end
+  end
+
   when_the_repository "has a cookbook with a nested LWRP" do
     before do
       directory 'cookbooks/x' do
 
-        file 'resources/do_nothing.rb', <<EOM
-actions :create, :nothing
-default_action :create
-EOM
-        file 'providers/do_nothing.rb', <<EOM
-action :create do
-end
-EOM
+        file 'resources/do_nothing.rb', <<-EOM
+          actions :create, :nothing
+          default_action :create
+        EOM
+        file 'providers/do_nothing.rb', <<-EOM
+          action :create do
+          end
+        EOM
 
-        file 'resources/my_machine.rb', <<EOM
-actions :create, :nothing
-default_action :create
-EOM
-        file 'providers/my_machine.rb', <<EOM
-use_inline_resources
-action :create do
-  x_do_nothing 'a'
-  x_do_nothing 'b'
-end
-EOM
+        file 'resources/my_machine.rb', <<-EOM
+          actions :create, :nothing
+          default_action :create
+        EOM
+        file 'providers/my_machine.rb', <<-EOM
+          use_inline_resources
+          action :create do
+            x_do_nothing 'a'
+            x_do_nothing 'b'
+          end
+        EOM
 
-        file 'recipes/default.rb', <<EOM
-x_my_machine "me"
-x_my_machine "you"
-EOM
+        file 'recipes/default.rb', <<-EOM
+          x_my_machine "me"
+          x_my_machine "you"
+        EOM
 
       end # directory 'cookbooks/x'
     end
diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_spec.rb
similarity index 50%
copy from spec/integration/recipes/lwrp_inline_resources_spec.rb
copy to spec/integration/recipes/lwrp_spec.rb
index b4c4e6c..7ecdfc7 100644
--- a/spec/integration/recipes/lwrp_inline_resources_spec.rb
+++ b/spec/integration/recipes/lwrp_spec.rb
@@ -1,11 +1,11 @@
 require 'support/shared/integration/integration_helper'
 require 'chef/mixin/shell_out'
 
-describe "LWRPs with inline resources" do
+describe "LWRPs" do
   include IntegrationSupport
   include Chef::Mixin::ShellOut
 
-  let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") }
+  let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) }
 
   # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
   # following constraints are satisfied:
@@ -18,34 +18,20 @@ describe "LWRPs with inline resources" do
   # cf. CHEF-4914
   let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" }
 
-  when_the_repository "has a cookbook with a nested LWRP" do
+  when_the_repository "has a cookbook named l-w-r-p" do
     before do
-      directory 'cookbooks/x' do
+      directory 'cookbooks/l-w-r-p' do
 
-        file 'resources/do_nothing.rb', <<EOM
-actions :create, :nothing
+        file 'resources/foo.rb', <<EOM
 default_action :create
 EOM
-        file 'providers/do_nothing.rb', <<EOM
+        file 'providers/foo.rb', <<EOM
 action :create do
 end
 EOM
 
-        file 'resources/my_machine.rb', <<EOM
-actions :create, :nothing
-default_action :create
-EOM
-        file 'providers/my_machine.rb', <<EOM
-use_inline_resources
-action :create do
-  x_do_nothing 'a'
-  x_do_nothing 'b'
-end
-EOM
-
         file 'recipes/default.rb', <<EOM
-x_my_machine "me"
-x_my_machine "you"
+l_w_r_p_foo "me"
 EOM
 
       end # directory 'cookbooks/x'
@@ -58,20 +44,9 @@ cookbook_path "#{path_to('cookbooks')}"
 log_level :warn
 EOM
 
-      result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'x::default'", :cwd => chef_dir)
-      actual = result.stdout.lines.map { |l| l.chomp }.join("\n")
-      expected = <<EOM
-  * x_my_machine[me] action create
-    * x_do_nothing[a] action create (up to date)
-    * x_do_nothing[b] action create (up to date)
-     (up to date)
-  * x_my_machine[you] action create
-    * x_do_nothing[a] action create (up to date)
-    * x_do_nothing[b] action create (up to date)
-     (up to date)
-EOM
-      expected = expected.lines.map { |l| l.chomp }.join("\n")
-      expect(actual).to include(expected)
+      result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'l-w-r-p::default'", :cwd => chef_dir)
+      expect(result.stdout).to match(/\* l_w_r_p_foo\[me\] action create \(up to date\)/)
+      expect(result.stdout).not_to match(/WARN: You are overriding l_w_r_p_foo/)
       result.error!
     end
   end
diff --git a/spec/integration/recipes/provider_choice.rb b/spec/integration/recipes/provider_choice.rb
new file mode 100644
index 0000000..01537b2
--- /dev/null
+++ b/spec/integration/recipes/provider_choice.rb
@@ -0,0 +1,36 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Recipe DSL methods" do
+  include IntegrationSupport
+
+  context "With resource class providing 'provider_thingy'" do
+    before :context do
+      class Chef::Resource::ProviderThingy < Chef::Resource
+        resource_name :provider_thingy
+        default_action :create
+        def to_s
+          "provider_thingy resource class"
+        end
+      end
+    end
+    context "And class Chef::Provider::ProviderThingy with no provides" do
+      before :context do
+        class Chef::Provider::ProviderThingy < Chef::Provider
+          def load_current_resource
+          end
+          def action_create
+            Chef::Log.warn("hello from #{self.class.name}")
+          end
+        end
+      end
+
+      it "provider_thingy 'blah' runs the provider and warns" do
+        recipe = converge {
+          provider_thingy 'blah' do; end
+        }
+        expect(recipe.logged_warnings).to match /hello from Chef::Provider::ProviderThingy/
+        expect(recipe.logged_warnings).to match /you must use 'provides' to provide DSL/i
+      end
+    end
+  end
+end
diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb
new file mode 100644
index 0000000..52bca87
--- /dev/null
+++ b/spec/integration/recipes/recipe_dsl_spec.rb
@@ -0,0 +1,1492 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Recipe DSL methods" do
+  include IntegrationSupport
+
+  module Namer
+    extend self
+    attr_accessor :current_index
+  end
+
+  before(:all) { Namer.current_index = 1 }
+  before { Namer.current_index += 1 }
+
+  context "with resource 'base_thingy' declared as BaseThingy" do
+    before(:context) {
+
+      class BaseThingy < Chef::Resource
+        resource_name 'base_thingy'
+        default_action :create
+
+        class<<self
+          attr_accessor :created_name
+          attr_accessor :created_resource
+          attr_accessor :created_provider
+        end
+
+        def provider
+          Provider
+        end
+        class Provider < Chef::Provider
+          def load_current_resource
+          end
+          def action_create
+            BaseThingy.created_name = new_resource.name
+            BaseThingy.created_resource = new_resource.class
+            BaseThingy.created_provider = self.class
+          end
+        end
+      end
+
+      # Modules to put stuff in
+      module RecipeDSLSpecNamespace; end
+      module RecipeDSLSpecNamespace::Bar; end
+
+    }
+
+    before :each do
+      BaseThingy.created_resource = nil
+      BaseThingy.created_provider = nil
+    end
+
+    it "creates base_thingy when you call base_thingy in a recipe" do
+      recipe = converge {
+        base_thingy 'blah' do; end
+      }
+      expect(recipe.logged_warnings).to eq ''
+      expect(BaseThingy.created_name).to eq 'blah'
+      expect(BaseThingy.created_resource).to eq BaseThingy
+    end
+
+    it "errors out when you call base_thingy do ... end in a recipe" do
+      expect_converge {
+        base_thingy do; end
+      }.to raise_error(ArgumentError, 'You must supply a name when declaring a base_thingy resource')
+    end
+
+    it "emits a warning when you call base_thingy 'foo', 'bar' do ... end in a recipe" do
+      Chef::Config[:treat_deprecation_warnings_as_errors] = false
+      recipe = converge {
+        base_thingy 'foo', 'bar' do
+        end
+      }
+      expect(recipe.logged_warnings).to match(/Cannot create resource base_thingy with more than one argument. All arguments except the name \("foo"\) will be ignored. This will cause an error in Chef 13. Arguments: \["foo", "bar"\]/)
+      expect(BaseThingy.created_name).to eq 'foo'
+      expect(BaseThingy.created_resource).to eq BaseThingy
+    end
+
+    context "Deprecated automatic resource DSL" do
+      before do
+        Chef::Config[:treat_deprecation_warnings_as_errors] = false
+      end
+
+      context "with a resource 'backcompat_thingy' declared in Chef::Resource and Chef::Provider" do
+        before(:context) {
+
+          class Chef::Resource::BackcompatThingy < Chef::Resource
+            default_action :create
+          end
+          class Chef::Provider::BackcompatThingy < Chef::Provider
+            def load_current_resource
+            end
+            def action_create
+              BaseThingy.created_resource = new_resource.class
+              BaseThingy.created_provider = self.class
+            end
+          end
+
+        }
+
+        it "backcompat_thingy creates a Chef::Resource::BackcompatThingy" do
+          recipe = converge {
+            backcompat_thingy 'blah' do; end
+          }
+          expect(BaseThingy.created_resource).to eq Chef::Resource::BackcompatThingy
+          expect(BaseThingy.created_provider).to eq Chef::Provider::BackcompatThingy
+        end
+
+        context "and another resource 'backcompat_thingy' in BackcompatThingy with 'provides'" do
+          before(:context) {
+
+            class RecipeDSLSpecNamespace::BackcompatThingy < BaseThingy
+              provides :backcompat_thingy
+              resource_name :backcompat_thingy
+            end
+
+          }
+
+          it "backcompat_thingy creates a BackcompatThingy" do
+            recipe = converge {
+              backcompat_thingy 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to match(/Class Chef::Provider::BackcompatThingy does not declare 'provides :backcompat_thingy'./)
+            expect(BaseThingy.created_resource).not_to be_nil
+          end
+        end
+      end
+
+      context "with a resource named RecipeDSLSpecNamespace::Bar::BarThingy" do
+        before(:context) {
+
+          class RecipeDSLSpecNamespace::Bar::BarThingy < BaseThingy
+          end
+
+        }
+
+        it "bar_thingy does not work" do
+          expect_converge {
+            bar_thingy 'blah' do; end
+          }.to raise_error(NoMethodError)
+        end
+      end
+
+      context "with a resource named Chef::Resource::NoNameThingy with resource_name nil" do
+        before(:context) {
+
+          class Chef::Resource::NoNameThingy < BaseThingy
+            resource_name nil
+          end
+
+        }
+
+        it "no_name_thingy does not work" do
+          expect_converge {
+            no_name_thingy 'blah' do; end
+          }.to raise_error(NoMethodError)
+        end
+      end
+
+      context "with a resource named AnotherNoNameThingy with resource_name :another_thingy_name" do
+        before(:context) {
+
+          class AnotherNoNameThingy < BaseThingy
+            resource_name :another_thingy_name
+          end
+
+        }
+
+        it "another_no_name_thingy does not work" do
+          expect_converge {
+            another_no_name_thingy 'blah' do; end
+          }.to raise_error(NoMethodError)
+        end
+
+        it "another_thingy_name works" do
+          recipe = converge {
+            another_thingy_name 'blah' do; end
+          }
+          expect(recipe.logged_warnings).to eq ''
+          expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy)
+        end
+      end
+
+      context "with a resource named AnotherNoNameThingy2 with resource_name :another_thingy_name2; resource_name :another_thingy_name3" do
+        before(:context) {
+
+          class AnotherNoNameThingy2 < BaseThingy
+            resource_name :another_thingy_name2
+            resource_name :another_thingy_name3
+          end
+
+        }
+
+        it "another_no_name_thingy does not work" do
+          expect_converge {
+            another_no_name_thingy2 'blah' do; end
+          }.to raise_error(NoMethodError)
+        end
+
+        it "another_thingy_name2 does not work" do
+          expect_converge {
+            another_thingy_name2 'blah' do; end
+          }.to raise_error(NoMethodError)
+        end
+
+        it "yet_another_thingy_name3 works" do
+          recipe = converge {
+            another_thingy_name3 'blah' do; end
+          }
+          expect(recipe.logged_warnings).to eq ''
+          expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy2)
+        end
+      end
+
+      context "provides overriding resource_name" do
+        context "with a resource named AnotherNoNameThingy3 with provides :another_no_name_thingy3, os: 'blarghle'" do
+          before(:context) {
+
+            class AnotherNoNameThingy3 < BaseThingy
+              resource_name :another_no_name_thingy_3
+              provides :another_no_name_thingy3, os: 'blarghle'
+            end
+
+          }
+
+          it "and os = linux, another_no_name_thingy3 does not work" do
+            expect_converge {
+              # TODO this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'linux'
+              another_no_name_thingy3 'blah' do; end
+            }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+          end
+
+          it "and os = blarghle, another_no_name_thingy3 works" do
+            recipe = converge {
+              # TODO this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'blarghle'
+              another_no_name_thingy3 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy3)
+          end
+        end
+
+        context "with a resource named AnotherNoNameThingy4 with two provides" do
+          before(:context) {
+
+            class AnotherNoNameThingy4 < BaseThingy
+              resource_name :another_no_name_thingy_4
+              provides :another_no_name_thingy4, os: 'blarghle'
+              provides :another_no_name_thingy4, platform_family: 'foo'
+            end
+
+          }
+
+          it "and os = linux, another_no_name_thingy4 does not work" do
+            expect_converge {
+              # TODO this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'linux'
+              another_no_name_thingy4 'blah' do; end
+            }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+          end
+
+          it "and os = blarghle, another_no_name_thingy4 works" do
+            recipe = converge {
+              # TODO this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'blarghle'
+              another_no_name_thingy4 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy4)
+          end
+
+          it "and platform_family = foo, another_no_name_thingy4 works" do
+            recipe = converge {
+              # TODO this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:platform_family] = 'foo'
+              another_no_name_thingy4 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy4)
+          end
+        end
+
+        context "with a resource named AnotherNoNameThingy5, a different resource_name, and a provides with the original resource_name" do
+          before(:context) {
+
+            class AnotherNoNameThingy5 < BaseThingy
+              resource_name :another_thingy_name_for_another_no_name_thingy5
+              provides :another_no_name_thingy5, os: 'blarghle'
+            end
+
+          }
+
+          it "and os = linux, another_no_name_thingy5 does not work" do
+            expect_converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'linux'
+              another_no_name_thingy5 'blah' do; end
+            }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+          end
+
+          it "and os = blarghle, another_no_name_thingy5 works" do
+            recipe = converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'blarghle'
+              another_no_name_thingy5 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy5)
+          end
+
+          it "the new resource name can be used in a recipe" do
+            recipe = converge {
+              another_thingy_name_for_another_no_name_thingy5 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy5)
+          end
+        end
+
+        context "with a resource named AnotherNoNameThingy6, a provides with the original resource name, and a different resource_name" do
+          before(:context) {
+
+            class AnotherNoNameThingy6 < BaseThingy
+              provides :another_no_name_thingy6, os: 'blarghle'
+              resource_name :another_thingy_name_for_another_no_name_thingy6
+            end
+
+          }
+
+          it "and os = linux, another_no_name_thingy6 does not work" do
+            expect_converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'linux'
+              another_no_name_thingy6 'blah' do; end
+            }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+          end
+
+          it "and os = blarghle, another_no_name_thingy6 works" do
+            recipe = converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'blarghle'
+              another_no_name_thingy6 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy6)
+          end
+
+          it "the new resource name can be used in a recipe" do
+            recipe = converge {
+              another_thingy_name_for_another_no_name_thingy6 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy6)
+          end
+        end
+
+        context "with a resource named AnotherNoNameThingy7, a new resource_name, and provides with that new resource name" do
+          before(:context) {
+
+            class AnotherNoNameThingy7 < BaseThingy
+              resource_name :another_thingy_name_for_another_no_name_thingy7
+              provides :another_thingy_name_for_another_no_name_thingy7, os: 'blarghle'
+            end
+
+          }
+
+          it "and os = linux, another_thingy_name_for_another_no_name_thingy7 does not work" do
+            expect_converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'linux'
+              another_thingy_name_for_another_no_name_thingy7 'blah' do; end
+            }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+          end
+
+          it "and os = blarghle, another_thingy_name_for_another_no_name_thingy7 works" do
+            recipe = converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'blarghle'
+              another_thingy_name_for_another_no_name_thingy7 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy7)
+          end
+
+          it "the old resource name does not work" do
+            expect_converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'linux'
+              another_no_name_thingy_7 'blah' do; end
+            }.to raise_error(NoMethodError)
+          end
+        end
+
+        # opposite order from the previous test (provides, then resource_name)
+        context "with a resource named AnotherNoNameThingy8, a provides with a new resource name, and resource_name with that new resource name" do
+          before(:context) {
+
+            class AnotherNoNameThingy8 < BaseThingy
+              provides :another_thingy_name_for_another_no_name_thingy8, os: 'blarghle'
+              resource_name :another_thingy_name_for_another_no_name_thingy8
+            end
+
+          }
+
+          it "and os = linux, another_thingy_name_for_another_no_name_thingy8 does not work" do
+            expect_converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'linux'
+              another_thingy_name_for_another_no_name_thingy8 'blah' do; end
+            }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+          end
+
+          it "and os = blarghle, another_thingy_name_for_another_no_name_thingy8 works" do
+            recipe = converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'blarghle'
+              another_thingy_name_for_another_no_name_thingy8 'blah' do; end
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy8)
+          end
+
+          it "the old resource name does not work" do
+            expect_converge {
+              # this is an ugly way to test, make Cheffish expose node attrs
+              run_context.node.automatic[:os] = 'linux'
+              another_thingy_name8 'blah' do; end
+            }.to raise_error(NoMethodError)
+          end
+        end
+      end
+    end
+
+    context "provides" do
+      context "when MySupplier provides :hemlock" do
+        before(:context) {
+
+          class RecipeDSLSpecNamespace::MySupplier < BaseThingy
+            resource_name :hemlock
+          end
+
+        }
+
+        it "my_supplier does not work in a recipe" do
+          expect_converge {
+            my_supplier 'blah' do; end
+          }.to raise_error(NoMethodError)
+        end
+
+        it "hemlock works in a recipe" do
+          expect_recipe {
+            hemlock 'blah' do; end
+          }.to emit_no_warnings_or_errors
+          expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::MySupplier
+        end
+      end
+
+      context "when Thingy3 has resource_name :thingy3" do
+        before(:context) {
+
+          class RecipeDSLSpecNamespace::Thingy3 < BaseThingy
+            resource_name :thingy3
+          end
+
+        }
+
+        it "thingy3 works in a recipe" do
+          expect_recipe {
+            thingy3 'blah' do; end
+          }.to emit_no_warnings_or_errors
+          expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+        end
+
+        context "and Thingy4 has resource_name :thingy3" do
+          before(:context) {
+
+            class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+              resource_name :thingy3
+            end
+
+          }
+
+          it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+            recipe = converge {
+              thingy3 'blah' do; end
+            }
+            expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+          end
+
+          it "thingy4 does not work in a recipe" do
+            expect_converge {
+              thingy4 'blah' do; end
+            }.to raise_error(NoMethodError)
+          end
+
+          it "resource_matching_short_name returns Thingy4" do
+            expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+          end
+        end
+      end
+
+      context "when Thingy5 has resource_name :thingy5 and provides :thingy5reverse, :thingy5_2 and :thingy5_2reverse" do
+        before(:context) {
+
+          class RecipeDSLSpecNamespace::Thingy5 < BaseThingy
+            resource_name :thingy5
+            provides :thingy5reverse
+            provides :thingy5_2
+            provides :thingy5_2reverse
+          end
+
+        }
+
+        it "thingy5 works in a recipe" do
+          expect_recipe {
+            thingy5 'blah' do; end
+          }.to emit_no_warnings_or_errors
+          expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+        end
+
+        context "and Thingy6 provides :thingy5" do
+          before(:context) {
+
+            class RecipeDSLSpecNamespace::Thingy6 < BaseThingy
+              resource_name :thingy6
+              provides :thingy5
+            end
+
+          }
+
+          it "thingy6 works in a recipe and yields Thingy6" do
+            recipe = converge {
+              thingy6 'blah' do; end
+            }
+            expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy6
+          end
+
+          it "thingy5 works in a recipe and yields Foo::Thingy5 (the alphabetical one)" do
+            recipe = converge {
+              thingy5 'blah' do; end
+            }
+            expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+          end
+
+          it "resource_matching_short_name returns Thingy5" do
+            expect(Chef::Resource.resource_matching_short_name(:thingy5)).to eq RecipeDSLSpecNamespace::Thingy5
+          end
+
+          context "and AThingy5 provides :thingy5reverse" do
+            before(:context) {
+
+              class RecipeDSLSpecNamespace::AThingy5 < BaseThingy
+                resource_name :thingy5reverse
+              end
+
+            }
+
+            it "thingy5reverse works in a recipe and yields AThingy5 (the alphabetical one)" do
+              recipe = converge {
+                thingy5reverse 'blah' do; end
+              }
+              expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::AThingy5
+            end
+          end
+
+          context "and ZRecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do
+            before(:context) {
+
+              module ZRecipeDSLSpecNamespace
+                class Thingy5 < BaseThingy
+                  resource_name :thingy5_2
+                end
+              end
+
+            }
+
+            it "thingy5_2 works in a recipe and yields the RecipeDSLSpaceNamespace one (the alphabetical one)" do
+              recipe = converge {
+                thingy5_2 'blah' do; end
+              }
+              expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+            end
+          end
+
+          context "and ARecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do
+            before(:context) {
+
+              module ARecipeDSLSpecNamespace
+                class Thingy5 < BaseThingy
+                  resource_name :thingy5_2reverse
+                end
+              end
+
+            }
+
+            it "thingy5_2reverse works in a recipe and yields the ARecipeDSLSpaceNamespace one (the alphabetical one)" do
+              recipe = converge {
+                thingy5_2reverse 'blah' do; end
+              }
+              expect(BaseThingy.created_resource).to eq ARecipeDSLSpecNamespace::Thingy5
+            end
+          end
+        end
+
+        context "when Thingy3 has resource_name :thingy3" do
+          before(:context) {
+
+            class RecipeDSLSpecNamespace::Thingy3 < BaseThingy
+              resource_name :thingy3
+            end
+
+          }
+
+          it "thingy3 works in a recipe" do
+            expect_recipe {
+              thingy3 'blah' do; end
+            }.to emit_no_warnings_or_errors
+            expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+          end
+
+          context "and Thingy4 has resource_name :thingy3" do
+            before(:context) {
+
+              class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+                resource_name :thingy3
+              end
+
+            }
+
+            it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+              recipe = converge {
+                thingy3 'blah' do; end
+              }
+              expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+            end
+
+            it "thingy4 does not work in a recipe" do
+              expect_converge {
+                thingy4 'blah' do; end
+              }.to raise_error(NoMethodError)
+            end
+
+            it "resource_matching_short_name returns Thingy4" do
+              expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+            end
+          end
+
+          context "and Thingy4 has resource_name :thingy3" do
+            before(:context) {
+
+              class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+                resource_name :thingy3
+              end
+
+            }
+
+            it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+              recipe = converge {
+                thingy3 'blah' do; end
+              }
+              expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+            end
+
+            it "thingy4 does not work in a recipe" do
+              expect_converge {
+                thingy4 'blah' do; end
+              }.to raise_error(NoMethodError)
+            end
+
+            it "resource_matching_short_name returns Thingy4" do
+              expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+            end
+          end
+        end
+
+      end
+
+      context "when Thingy7 provides :thingy8" do
+        before(:context) {
+
+          class RecipeDSLSpecNamespace::Thingy7 < BaseThingy
+            resource_name :thingy7
+            provides :thingy8
+          end
+
+        }
+
+        context "and Thingy8 has resource_name :thingy8" do
+          before(:context) {
+
+            class RecipeDSLSpecNamespace::Thingy8 < BaseThingy
+              resource_name :thingy8
+            end
+
+          }
+
+          it "thingy7 works in a recipe and yields Thingy7" do
+            recipe = converge {
+              thingy7 'blah' do; end
+            }
+            expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7
+          end
+
+          it "thingy8 works in a recipe and yields Thingy7 (alphabetical)" do
+            recipe = converge {
+              thingy8 'blah' do; end
+            }
+            expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7
+          end
+
+          it "resource_matching_short_name returns Thingy8" do
+            expect(Chef::Resource.resource_matching_short_name(:thingy8)).to eq RecipeDSLSpecNamespace::Thingy8
+          end
+        end
+      end
+
+      context "when Thingy12 provides :thingy12, :twizzle and :twizzle2" do
+        before(:context) {
+
+          class RecipeDSLSpecNamespace::Thingy12 < BaseThingy
+            resource_name :thingy12
+            provides :twizzle
+            provides :twizzle2
+          end
+
+        }
+
+        it "thingy12 works in a recipe and yields Thingy12" do
+          expect_recipe {
+            thingy12 'blah' do; end
+          }.to emit_no_warnings_or_errors
+          expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
+        end
+
+        it "twizzle works in a recipe and yields Thingy12" do
+          expect_recipe {
+            twizzle 'blah' do; end
+          }.to emit_no_warnings_or_errors
+          expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
+        end
+
+        it "twizzle2 works in a recipe and yields Thingy12" do
+          expect_recipe {
+            twizzle2 'blah' do; end
+          }.to emit_no_warnings_or_errors
+          expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
+        end
+      end
+
+      context "with platform-specific resources 'my_super_thingy_foo' and 'my_super_thingy_bar'" do
+        before(:context) {
+          class MySuperThingyFoo < BaseThingy
+            resource_name :my_super_thingy_foo
+            provides :my_super_thingy, platform: 'foo'
+          end
+
+          class MySuperThingyBar < BaseThingy
+            resource_name :my_super_thingy_bar
+            provides :my_super_thingy, platform: 'bar'
+          end
+        }
+
+        it "A run with platform 'foo' uses MySuperThingyFoo" do
+          r = Cheffish::ChefRun.new(chef_config)
+          r.client.run_context.node.automatic['platform'] = 'foo'
+          r.compile_recipe {
+            my_super_thingy 'blah' do; end
+          }
+          r.converge
+          expect(r).to emit_no_warnings_or_errors
+          expect(BaseThingy.created_resource).to eq MySuperThingyFoo
+        end
+
+        it "A run with platform 'bar' uses MySuperThingyBar" do
+          r = Cheffish::ChefRun.new(chef_config)
+          r.client.run_context.node.automatic['platform'] = 'bar'
+          r.compile_recipe {
+            my_super_thingy 'blah' do; end
+          }
+          r.converge
+          expect(r).to emit_no_warnings_or_errors
+          expect(BaseThingy.created_resource).to eq MySuperThingyBar
+        end
+
+        it "A run with platform 'x' reports that my_super_thingy is not supported" do
+          r = Cheffish::ChefRun.new(chef_config)
+          r.client.run_context.node.automatic['platform'] = 'x'
+          expect {
+            r.compile_recipe {
+              my_super_thingy 'blah' do; end
+            }
+          }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+        end
+      end
+
+      context "when Thingy10 provides :thingy10" do
+        before(:context) {
+          class RecipeDSLSpecNamespace::Thingy10 < BaseThingy
+            resource_name :thingy10
+          end
+        }
+
+        it "declaring a resource providing the same :thingy10 with override: true does not produce a warning" do
+          expect(Chef::Log).not_to receive(:warn)
+          class RecipeDSLSpecNamespace::Thingy10AlternateProvider < BaseThingy
+            provides :thingy10, override: true
+          end
+        end
+      end
+
+      context "when Thingy11 provides :thingy11" do
+        before(:context) {
+          class RecipeDSLSpecNamespace::Thingy11 < BaseThingy
+            resource_name :thingy10
+          end
+        }
+
+        it "declaring a resource providing the same :thingy11 with os: 'linux' does not produce a warning" do
+          expect(Chef::Log).not_to receive(:warn)
+          class RecipeDSLSpecNamespace::Thingy11AlternateProvider < BaseThingy
+            provides :thingy11, os: 'linux'
+          end
+        end
+      end
+    end
+
+    context "with a resource named 'B' with resource name :two_classes_one_dsl" do
+      let(:two_classes_one_dsl) { :"two_classes_one_dsl#{Namer.current_index}" }
+      let(:resource_class) {
+        result = Class.new(BaseThingy) do
+          def self.name
+            "B"
+          end
+          def self.to_s; name; end
+          def self.inspect; name.inspect; end
+        end
+        result.resource_name two_classes_one_dsl
+        result
+      }
+      before { resource_class } # pull on it so it gets defined before the recipe runs
+
+      context "and another resource named 'A' with resource_name :two_classes_one_dsl" do
+        let(:resource_class_a) {
+          result = Class.new(BaseThingy) do
+            def self.name
+              "A"
+            end
+            def self.to_s; name; end
+            def self.inspect; name.inspect; end
+          end
+          result.resource_name two_classes_one_dsl
+          result
+        }
+        before { resource_class_a } # pull on it so it gets defined before the recipe runs
+
+        it "two_classes_one_dsl resolves to A (alphabetically earliest)" do
+          two_classes_one_dsl = self.two_classes_one_dsl
+          recipe = converge {
+            instance_eval("#{two_classes_one_dsl} 'blah'")
+          }
+          expect(recipe.logged_warnings).to eq ''
+          expect(BaseThingy.created_resource).to eq resource_class_a
+        end
+
+        it "resource_matching_short_name returns B" do
+          expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class_a
+        end
+      end
+
+      context "and another resource named 'Z' with resource_name :two_classes_one_dsl" do
+        let(:resource_class_z) {
+          result = Class.new(BaseThingy) do
+            def self.name
+              "Z"
+            end
+            def self.to_s; name; end
+            def self.inspect; name.inspect; end
+          end
+          result.resource_name two_classes_one_dsl
+          result
+        }
+        before { resource_class_z } # pull on it so it gets defined before the recipe runs
+
+        it "two_classes_one_dsl resolves to B (alphabetically earliest)" do
+          two_classes_one_dsl = self.two_classes_one_dsl
+          recipe = converge {
+            instance_eval("#{two_classes_one_dsl} 'blah'")
+          }
+          expect(recipe.logged_warnings).to eq ''
+          expect(BaseThingy.created_resource).to eq resource_class
+        end
+
+        it "resource_matching_short_name returns B" do
+          expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+        end
+
+        context "and a priority array [ Z, B ]" do
+          before do
+            Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z, resource_class ])
+          end
+
+          it "two_classes_one_dsl resolves to Z (respects the priority array)" do
+            two_classes_one_dsl = self.two_classes_one_dsl
+            recipe = converge {
+              instance_eval("#{two_classes_one_dsl} 'blah'")
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq resource_class_z
+          end
+
+          it "resource_matching_short_name returns B" do
+            expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+          end
+
+          context "when Z provides(:two_classes_one_dsl) { false }" do
+            before do
+              resource_class_z.provides(two_classes_one_dsl) { false }
+            end
+
+            it "two_classes_one_dsl resolves to B (picks the next thing in the priority array)" do
+              two_classes_one_dsl = self.two_classes_one_dsl
+              recipe = converge {
+                instance_eval("#{two_classes_one_dsl} 'blah'")
+              }
+              expect(recipe.logged_warnings).to eq ''
+              expect(BaseThingy.created_resource).to eq resource_class
+            end
+
+            it "resource_matching_short_name returns B" do
+              expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+            end
+          end
+        end
+
+        context "and priority arrays [ B ] and [ Z ]" do
+          before do
+            Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class ])
+            Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ])
+          end
+
+          it "two_classes_one_dsl resolves to Z (respects the most recent priority array)" do
+            two_classes_one_dsl = self.two_classes_one_dsl
+            recipe = converge {
+              instance_eval("#{two_classes_one_dsl} 'blah'")
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq resource_class_z
+          end
+
+          it "resource_matching_short_name returns B" do
+            expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+          end
+
+          context "when Z provides(:two_classes_one_dsl) { false }" do
+            before do
+              resource_class_z.provides(two_classes_one_dsl) { false }
+            end
+
+            it "two_classes_one_dsl resolves to B (picks the first match from the other priority array)" do
+              two_classes_one_dsl = self.two_classes_one_dsl
+              recipe = converge {
+                instance_eval("#{two_classes_one_dsl} 'blah'")
+              }
+              expect(recipe.logged_warnings).to eq ''
+              expect(BaseThingy.created_resource).to eq resource_class
+            end
+
+            it "resource_matching_short_name returns B" do
+              expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+            end
+          end
+        end
+
+        context "and a priority array [ Z ]" do
+          before do
+            Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ])
+          end
+
+          context "when Z provides(:two_classes_one_dsl) { false }" do
+            before do
+              resource_class_z.provides(two_classes_one_dsl) { false }
+            end
+
+            it "two_classes_one_dsl resolves to B (picks the first match outside the priority array)" do
+              two_classes_one_dsl = self.two_classes_one_dsl
+              recipe = converge {
+                instance_eval("#{two_classes_one_dsl} 'blah'")
+              }
+              expect(recipe.logged_warnings).to eq ''
+              expect(BaseThingy.created_resource).to eq resource_class
+            end
+
+            it "resource_matching_short_name returns B" do
+              expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+            end
+          end
+        end
+
+      end
+
+      context "and a provider named 'B' which provides :two_classes_one_dsl" do
+        before do
+          resource_class.send(:define_method, :provider) { nil }
+        end
+
+        let(:provider_class) {
+          result = Class.new(BaseThingy::Provider) do
+            def self.name
+              "B"
+            end
+            def self.to_s; name; end
+            def self.inspect; name.inspect; end
+          end
+          result.provides two_classes_one_dsl
+          result
+        }
+        before { provider_class } # pull on it so it gets defined before the recipe runs
+
+        context "and another provider named 'A'" do
+          let(:provider_class_a) {
+            result = Class.new(BaseThingy::Provider) do
+              def self.name
+                "A"
+              end
+              def self.to_s; name; end
+              def self.inspect; name.inspect; end
+            end
+            result
+          }
+          context "which provides :two_classes_one_dsl" do
+            before { provider_class_a.provides two_classes_one_dsl }
+
+            it "two_classes_one_dsl resolves to A (alphabetically earliest)" do
+              two_classes_one_dsl = self.two_classes_one_dsl
+              recipe = converge {
+                instance_eval("#{two_classes_one_dsl} 'blah'")
+              }
+              expect(recipe.logged_warnings).to eq ''
+              expect(BaseThingy.created_provider).to eq provider_class_a
+            end
+          end
+          context "which provides(:two_classes_one_dsl) { false }" do
+            before { provider_class_a.provides(two_classes_one_dsl) { false } }
+
+            it "two_classes_one_dsl resolves to B (since A declined)" do
+              two_classes_one_dsl = self.two_classes_one_dsl
+              recipe = converge {
+                instance_eval("#{two_classes_one_dsl} 'blah'")
+              }
+              expect(recipe.logged_warnings).to eq ''
+              expect(BaseThingy.created_provider).to eq provider_class
+            end
+          end
+        end
+
+        context "and another provider named 'Z'" do
+          let(:provider_class_z) {
+            result = Class.new(BaseThingy::Provider) do
+              def self.name
+                "Z"
+              end
+              def self.to_s; name; end
+              def self.inspect; name.inspect; end
+            end
+            result
+          }
+          before { provider_class_z } # pull on it so it gets defined before the recipe runs
+
+          context "which provides :two_classes_one_dsl" do
+            before { provider_class_z.provides two_classes_one_dsl }
+
+            it "two_classes_one_dsl resolves to B (alphabetically earliest)" do
+              two_classes_one_dsl = self.two_classes_one_dsl
+              recipe = converge {
+                instance_eval("#{two_classes_one_dsl} 'blah'")
+              }
+              expect(recipe.logged_warnings).to eq ''
+              expect(BaseThingy.created_provider).to eq provider_class
+            end
+
+            context "with a priority array [ Z, B ]" do
+              before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] }
+
+              it "two_classes_one_dsl resolves to Z (respects the priority map)" do
+                two_classes_one_dsl = self.two_classes_one_dsl
+                recipe = converge {
+                  instance_eval("#{two_classes_one_dsl} 'blah'")
+                }
+                expect(recipe.logged_warnings).to eq ''
+                expect(BaseThingy.created_provider).to eq provider_class_z
+              end
+            end
+          end
+
+          context "which provides(:two_classes_one_dsl) { false }" do
+            before { provider_class_z.provides(two_classes_one_dsl) { false } }
+
+            context "with a priority array [ Z, B ]" do
+              before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] }
+
+              it "two_classes_one_dsl resolves to B (the next one in the priority map)" do
+                two_classes_one_dsl = self.two_classes_one_dsl
+                recipe = converge {
+                  instance_eval("#{two_classes_one_dsl} 'blah'")
+                }
+                expect(recipe.logged_warnings).to eq ''
+                expect(BaseThingy.created_provider).to eq provider_class
+              end
+            end
+
+            context "with priority arrays [ B ] and [ Z ]" do
+              before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z ] }
+              before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class ] }
+
+              it "two_classes_one_dsl resolves to B (the one in the next priority map)" do
+                two_classes_one_dsl = self.two_classes_one_dsl
+                recipe = converge {
+                  instance_eval("#{two_classes_one_dsl} 'blah'")
+                }
+                expect(recipe.logged_warnings).to eq ''
+                expect(BaseThingy.created_provider).to eq provider_class
+              end
+            end
+          end
+        end
+      end
+
+      context "and another resource Blarghle with provides :two_classes_one_dsl, os: 'blarghle'" do
+        let(:resource_class_blarghle) {
+          result = Class.new(BaseThingy) do
+            def self.name
+              "Blarghle"
+            end
+            def self.to_s; name; end
+            def self.inspect; name.inspect; end
+          end
+          result.resource_name two_classes_one_dsl
+          result.provides two_classes_one_dsl, os: 'blarghle'
+          result
+        }
+        before { resource_class_blarghle } # pull on it so it gets defined before the recipe runs
+
+        it "on os = blarghle, two_classes_one_dsl resolves to Blarghle" do
+          two_classes_one_dsl = self.two_classes_one_dsl
+          recipe = converge {
+            # this is an ugly way to test, make Cheffish expose node attrs
+            run_context.node.automatic[:os] = 'blarghle'
+            instance_eval("#{two_classes_one_dsl} 'blah' do; end")
+          }
+          expect(recipe.logged_warnings).to eq ''
+          expect(BaseThingy.created_resource).to eq resource_class_blarghle
+        end
+
+        it "on os = linux, two_classes_one_dsl resolves to B" do
+          two_classes_one_dsl = self.two_classes_one_dsl
+          recipe = converge {
+            # this is an ugly way to test, make Cheffish expose node attrs
+            run_context.node.automatic[:os] = 'linux'
+            instance_eval("#{two_classes_one_dsl} 'blah' do; end")
+          }
+          expect(recipe.logged_warnings).to eq ''
+          expect(BaseThingy.created_resource).to eq resource_class
+        end
+      end
+    end
+
+    context "with a resource MyResource" do
+      let(:resource_class) { Class.new(BaseThingy) do
+        def self.called_provides
+          @called_provides
+        end
+        def to_s
+          "MyResource"
+        end
+      end }
+      let(:my_resource) { :"my_resource#{Namer.current_index}" }
+      let(:blarghle_blarghle_little_star) { :"blarghle_blarghle_little_star#{Namer.current_index}" }
+
+      context "with resource_name :my_resource" do
+        before {
+          resource_class.resource_name my_resource
+        }
+
+        context "with provides? returning true to my_resource" do
+          before {
+            my_resource = self.my_resource
+            resource_class.define_singleton_method(:provides?) do |node, resource_name|
+              @called_provides = true
+              resource_name == my_resource
+            end
+          }
+
+          it "my_resource returns the resource and calls provides?, but does not emit a warning" do
+            dsl_name = self.my_resource
+            recipe = converge {
+              instance_eval("#{dsl_name} 'foo'")
+            }
+            expect(recipe.logged_warnings).to eq ''
+            expect(BaseThingy.created_resource).to eq resource_class
+            expect(resource_class.called_provides).to be_truthy
+          end
+        end
+
+        context "with provides? returning true to blarghle_blarghle_little_star and not resource_name" do
+          before do
+            blarghle_blarghle_little_star = self.blarghle_blarghle_little_star
+            resource_class.define_singleton_method(:provides?) do |node, resource_name|
+              @called_provides = true
+              resource_name == blarghle_blarghle_little_star
+            end
+          end
+
+          it "my_resource does not return the resource" do
+            dsl_name = self.my_resource
+            expect_converge {
+              instance_eval("#{dsl_name} 'foo'")
+            }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+            expect(resource_class.called_provides).to be_truthy
+          end
+
+          it "blarghle_blarghle_little_star 'foo' returns the resource and emits a warning" do
+            Chef::Config[:treat_deprecation_warnings_as_errors] = false
+            dsl_name = self.blarghle_blarghle_little_star
+            recipe = converge {
+              instance_eval("#{dsl_name} 'foo'")
+            }
+            expect(recipe.logged_warnings).to include "WARN: #{resource_class}.provides? returned true when asked if it provides DSL #{dsl_name}, but provides :#{dsl_name} was never called!"
+            expect(BaseThingy.created_resource).to eq resource_class
+            expect(resource_class.called_provides).to be_truthy
+          end
+        end
+
+        context "and a provider" do
+          let(:provider_class) do
+            Class.new(BaseThingy::Provider) do
+              def self.name
+                "MyProvider"
+              end
+              def self.to_s; name; end
+              def self.inspect; name.inspect; end
+              def self.called_provides
+                @called_provides
+              end
+            end
+          end
+
+          before do
+            resource_class.send(:define_method, :provider) { nil }
+          end
+
+          context "that provides :my_resource" do
+            before do
+              provider_class.provides my_resource
+            end
+
+            context "with supports? returning true" do
+              before do
+                provider_class.define_singleton_method(:supports?) { |resource,action| true }
+              end
+
+              it "my_resource runs the provider and does not emit a warning" do
+                my_resource = self.my_resource
+                recipe = converge {
+                  instance_eval("#{my_resource} 'foo'")
+                }
+                expect(recipe.logged_warnings).to eq ''
+                expect(BaseThingy.created_provider).to eq provider_class
+              end
+
+              context "and another provider supporting :my_resource with supports? false" do
+                let(:provider_class2) do
+                  Class.new(BaseThingy::Provider) do
+                    def self.name
+                      "MyProvider2"
+                    end
+                    def self.to_s; name; end
+                    def self.inspect; name.inspect; end
+                    def self.called_provides
+                      @called_provides
+                    end
+                    provides my_resource
+                    def self.supports?(resource, action)
+                      false
+                    end
+                  end
+                end
+
+                it "my_resource runs the first provider" do
+                  my_resource = self.my_resource
+                  recipe = converge {
+                    instance_eval("#{my_resource} 'foo'")
+                  }
+                  expect(recipe.logged_warnings).to eq ''
+                  expect(BaseThingy.created_provider).to eq provider_class
+                end
+              end
+            end
+
+            context "with supports? returning false" do
+              before do
+                provider_class.define_singleton_method(:supports?) { |resource,action| false }
+              end
+
+              # TODO no warning? ick
+              it "my_resource runs the provider anyway" do
+                my_resource = self.my_resource
+                recipe = converge {
+                  instance_eval("#{my_resource} 'foo'")
+                }
+                expect(recipe.logged_warnings).to eq ''
+                expect(BaseThingy.created_provider).to eq provider_class
+              end
+
+              context "and another provider supporting :my_resource with supports? true" do
+                let(:provider_class2) do
+                  my_resource = self.my_resource
+                  Class.new(BaseThingy::Provider) do
+                    def self.name
+                      "MyProvider2"
+                    end
+                    def self.to_s; name; end
+                    def self.inspect; name.inspect; end
+                    def self.called_provides
+                      @called_provides
+                    end
+                    provides my_resource
+                    def self.supports?(resource, action)
+                      true
+                    end
+                  end
+                end
+                before { provider_class2 } # make sure the provider class shows up
+
+                it "my_resource runs the other provider" do
+                  my_resource = self.my_resource
+                  recipe = converge {
+                    instance_eval("#{my_resource} 'foo'")
+                  }
+                  expect(recipe.logged_warnings).to eq ''
+                  expect(BaseThingy.created_provider).to eq provider_class2
+                end
+              end
+            end
+          end
+
+          context "with provides? returning true" do
+            before {
+              my_resource = self.my_resource
+              provider_class.define_singleton_method(:provides?) do |node, resource|
+                @called_provides = true
+                resource.declared_type == my_resource
+              end
+            }
+
+            context "that provides :my_resource" do
+              before {
+                provider_class.provides my_resource
+              }
+
+              it "my_resource calls the provider (and calls provides?), but does not emit a warning" do
+                my_resource = self.my_resource
+                recipe = converge {
+                  instance_eval("#{my_resource} 'foo'")
+                }
+                expect(recipe.logged_warnings).to eq ''
+                expect(BaseThingy.created_provider).to eq provider_class
+                expect(provider_class.called_provides).to be_truthy
+              end
+            end
+
+            context "that does not call provides :my_resource" do
+              it "my_resource calls the provider (and calls provides?), and emits a warning" do
+                Chef::Config[:treat_deprecation_warnings_as_errors] = false
+                my_resource = self.my_resource
+                recipe = converge {
+                  instance_eval("#{my_resource} 'foo'")
+                }
+                expect(recipe.logged_warnings).to include("WARN: #{provider_class}.provides? returned true when asked if it provides DSL #{my_resource}, but provides :#{my_resource} was never called!")
+                expect(BaseThingy.created_provider).to eq provider_class
+                expect(provider_class.called_provides).to be_truthy
+              end
+            end
+          end
+
+          context "with provides? returning false to my_resource" do
+            before {
+              my_resource = self.my_resource
+              provider_class.define_singleton_method(:provides?) do |node, resource|
+                @called_provides = true
+                false
+              end
+            }
+
+            context "that provides :my_resource" do
+              before {
+                provider_class.provides my_resource
+              }
+
+              it "my_resource fails to find a provider (and calls provides)" do
+                my_resource = self.my_resource
+                expect_converge {
+                  instance_eval("#{my_resource} 'foo'")
+                }.to raise_error(Chef::Exceptions::ProviderNotFound)
+                expect(provider_class.called_provides).to be_truthy
+              end
+            end
+
+            context "that does not provide :my_resource" do
+              it "my_resource fails to find a provider (and calls provides)" do
+                my_resource = self.my_resource
+                expect_converge {
+                  instance_eval("#{my_resource} 'foo'")
+                }.to raise_error(Chef::Exceptions::ProviderNotFound)
+                expect(provider_class.called_provides).to be_truthy
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+
+  before(:all) { Namer.current_index = 0 }
+  before { Namer.current_index += 1 }
+
+  context "with an LWRP that declares actions" do
+    let(:resource_class) {
+      Class.new(Chef::Resource::LWRPBase) do
+        provides :"recipe_dsl_spec#{Namer.current_index}"
+        actions :create
+      end
+    }
+    let(:resource) {
+      resource_class.new("blah", run_context)
+    }
+    it "The actions are part of actions along with :nothing" do
+      expect(resource_class.actions).to eq [ :nothing, :create ]
+    end
+    it "The actions are part of allowed_actions along with :nothing" do
+      expect(resource.allowed_actions).to eq [ :nothing, :create ]
+    end
+
+    context "and a subclass that declares more actions" do
+      let(:subresource_class) {
+        Class.new(Chef::Resource::LWRPBase) do
+          provides :"recipe_dsl_spec_sub#{Namer.current_index}"
+          actions :delete
+        end
+      }
+      let(:subresource) {
+        subresource_class.new("subblah", run_context)
+      }
+
+      it "The parent class actions are not part of actions" do
+        expect(subresource_class.actions).to eq [ :nothing, :delete ]
+      end
+      it "The parent class actions are not part of allowed_actions" do
+        expect(subresource.allowed_actions).to eq [ :nothing, :delete ]
+      end
+      it "The parent class actions do not change" do
+        expect(resource_class.actions).to eq [ :nothing, :create ]
+        expect(resource.allowed_actions).to eq [ :nothing, :create ]
+      end
+    end
+  end
+
+  context "with a dynamically defined resource and regular provider" do
+    before(:context) do
+      Class.new(Chef::Resource) do
+        resource_name :lw_resource_with_hw_provider_test_case
+        default_action :create
+        attr_accessor :created_provider
+      end
+      class Chef::Provider::LwResourceWithHwProviderTestCase < Chef::Provider
+        def load_current_resource
+        end
+        def action_create
+          new_resource.created_provider = self.class
+        end
+      end
+    end
+
+    it "looks up the provider in Chef::Provider converting the resource name from snake case to camel case" do
+      resource = nil
+      recipe = converge {
+        resource = lw_resource_with_hw_provider_test_case 'blah' do; end
+      }
+      expect(resource.created_provider).to eq(Chef::Provider::LwResourceWithHwProviderTestCase)
+    end
+  end
+end
diff --git a/spec/integration/recipes/remote_directory.rb b/spec/integration/recipes/remote_directory.rb
new file mode 100644
index 0000000..a1988cc
--- /dev/null
+++ b/spec/integration/recipes/remote_directory.rb
@@ -0,0 +1,74 @@
+require 'support/shared/integration/integration_helper'
+
+describe Chef::Resource::RemoteDirectory do
+  include IntegrationSupport
+  include Chef::Mixin::ShellOut
+
+  # Until Cheffish::RSpec has cookbook support, we have to run the whole client
+  let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") }
+
+  # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
+  # following constraints are satisfied:
+  # * Windows: windows can only run batch scripts as bare executables. Rubygems
+  # creates batch wrappers for installed gems, but we don't have batch wrappers
+  # in the source tree.
+  # * Other `chef-client` in PATH: A common case is running the tests on a
+  # machine that has omnibus chef installed. In that case we need to ensure
+  # we're running `chef-client` from the source tree and not the external one.
+  # cf. CHEF-4914
+  let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" }
+
+  when_the_repository "has a cookbook with a source_dir with two subdirectories, each with one file and subdir in a different alphabetical order" do
+    before do
+      file 'config/client.rb', <<-EOM
+        local_mode true
+        cookbook_path "#{path_to('cookbooks')}"
+      EOM
+      directory "cookbooks/test" do
+        directory "files/default/source_dir" do
+          directory "sub1" do
+            file "aaa", ""
+            file "zzz/file", ""
+          end
+          directory "sub2" do
+            file "aaa/file", ""
+            file "zzz", ""
+          end
+        end
+      end
+    end
+
+    context "and a recipe is run with a remote_directory that syncs source_dir with different mode and file_mode" do
+      let!(:dest_dir) { path_to("dest_dir") }
+      before do
+        directory "cookbooks/test" do
+          file "recipes/default.rb", <<-EOM
+             remote_directory #{dest_dir.inspect} do
+               source "source_dir"
+               mode "0754"
+               files_mode 0777
+             end
+          EOM
+        end
+        shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'test::default'", :cwd => chef_dir)
+      end
+
+      def mode_of(path)
+        path = path_to(path)
+        stat = File.stat(path)
+        (stat.mode & 0777).to_s(8)
+      end
+
+      it "creates all directories and files with the correct permissions" do
+        expect(mode_of("dest_dir/sub1")).to eq "754"
+        expect(mode_of("dest_dir/sub1/aaa")).to eq "777"
+        expect(mode_of("dest_dir/sub1/zzz")).to eq "754"
+        expect(mode_of("dest_dir/sub1/zzz/file")).to eq "777"
+        expect(mode_of("dest_dir/sub2")).to eq "754"
+        expect(mode_of("dest_dir/sub2/aaa")).to eq "754"
+        expect(mode_of("dest_dir/sub2/aaa/file")).to eq "777"
+        expect(mode_of("dest_dir/sub2/zzz")).to eq "777"
+      end
+    end
+  end
+end
diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb
new file mode 100644
index 0000000..a0e0dbb
--- /dev/null
+++ b/spec/integration/recipes/resource_action_spec.rb
@@ -0,0 +1,458 @@
+require 'support/shared/integration/integration_helper'
+
+# Houses any classes we declare
+module ResourceActionSpec
+
+describe "Resource.action" do
+  include IntegrationSupport
+
+  shared_context "ActionJackson" do
+    it "the default action is the first declared action" do
+      converge <<-EOM, __FILE__, __LINE__+1
+        #{resource_dsl} 'hi' do
+          foo 'foo!'
+        end
+      EOM
+      expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+      expect(ActionJackson.succeeded).to eq true
+    end
+
+    it "the action can access recipe DSL" do
+      converge <<-EOM, __FILE__, __LINE__+1
+        #{resource_dsl} 'hi' do
+          foo 'foo!'
+          action :access_recipe_dsl
+        end
+      EOM
+      expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+      expect(ActionJackson.succeeded).to eq true
+    end
+
+    it "the action can access attributes" do
+      converge <<-EOM, __FILE__, __LINE__+1
+        #{resource_dsl} 'hi' do
+          foo 'foo!'
+          action :access_attribute
+        end
+      EOM
+      expect(ActionJackson.ran_action).to eq :access_attribute
+      expect(ActionJackson.succeeded).to eq 'foo!'
+    end
+
+    it "the action can access public methods" do
+      converge <<-EOM, __FILE__, __LINE__+1
+        #{resource_dsl} 'hi' do
+          foo 'foo!'
+          action :access_method
+        end
+      EOM
+      expect(ActionJackson.ran_action).to eq :access_method
+      expect(ActionJackson.succeeded).to eq 'foo_public!'
+    end
+
+    it "the action can access protected methods" do
+      converge <<-EOM, __FILE__, __LINE__+1
+        #{resource_dsl} 'hi' do
+          foo 'foo!'
+          action :access_protected_method
+        end
+      EOM
+      expect(ActionJackson.ran_action).to eq :access_protected_method
+      expect(ActionJackson.succeeded).to eq 'foo_protected!'
+    end
+
+    it "the action cannot access private methods" do
+      expect {
+        converge(<<-EOM, __FILE__, __LINE__+1)
+          #{resource_dsl} 'hi' do
+            foo 'foo!'
+            action :access_private_method
+          end
+        EOM
+      }.to raise_error(NameError)
+      expect(ActionJackson.ran_action).to eq :access_private_method
+    end
+
+    it "the action cannot access resource instance variables" do
+      converge <<-EOM, __FILE__, __LINE__+1
+        #{resource_dsl} 'hi' do
+          foo 'foo!'
+          action :access_instance_variable
+        end
+      EOM
+      expect(ActionJackson.ran_action).to eq :access_instance_variable
+      expect(ActionJackson.succeeded).to be_nil
+    end
+
+    it "the action does not compile until the prior resource has converged" do
+      converge <<-EOM, __FILE__, __LINE__+1
+        ruby_block 'wow' do
+          block do
+            ResourceActionSpec::ActionJackson.ruby_block_converged = 'ruby_block_converged!'
+          end
+        end
+
+        #{resource_dsl} 'hi' do
+          foo 'foo!'
+          action :access_class_method
+        end
+      EOM
+      expect(ActionJackson.ran_action).to eq :access_class_method
+      expect(ActionJackson.succeeded).to eq 'ruby_block_converged!'
+    end
+
+    it "the action's resources converge before the next resource converges" do
+      converge <<-EOM, __FILE__, __LINE__+1
+        #{resource_dsl} 'hi' do
+          foo 'foo!'
+          action :access_attribute
+        end
+
+        ruby_block 'wow' do
+          block do
+            ResourceActionSpec::ActionJackson.ruby_block_converged = ResourceActionSpec::ActionJackson.succeeded
+          end
+        end
+      EOM
+      expect(ActionJackson.ran_action).to eq :access_attribute
+      expect(ActionJackson.succeeded).to eq 'foo!'
+      expect(ActionJackson.ruby_block_converged).to eq 'foo!'
+    end
+  end
+
+  context "With resource 'action_jackson'" do
+    class ActionJackson < Chef::Resource
+      use_automatic_resource_name
+      def foo(value=nil)
+        @foo = value if value
+        @foo
+      end
+      def blarghle(value=nil)
+        @blarghle = value if value
+        @blarghle
+      end
+
+      class <<self
+        attr_accessor :ran_action
+        attr_accessor :succeeded
+        attr_accessor :ruby_block_converged
+      end
+
+      public
+      def foo_public
+        'foo_public!'
+      end
+      protected
+      def foo_protected
+        'foo_protected!'
+      end
+      private
+      def foo_private
+        'foo_private!'
+      end
+
+      public
+      action :access_recipe_dsl do
+        ActionJackson.ran_action = :access_recipe_dsl
+        ruby_block 'hi there' do
+          block do
+            ActionJackson.succeeded = true
+          end
+        end
+      end
+      action :access_attribute do
+        ActionJackson.ran_action = :access_attribute
+        ActionJackson.succeeded = foo
+        ActionJackson.succeeded += " #{blarghle}" if blarghle
+        ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
+      end
+      action :access_attribute2 do
+        ActionJackson.ran_action = :access_attribute2
+        ActionJackson.succeeded = foo
+        ActionJackson.succeeded += " #{blarghle}" if blarghle
+        ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
+      end
+      action :access_method do
+        ActionJackson.ran_action = :access_method
+        ActionJackson.succeeded = foo_public
+      end
+      action :access_protected_method do
+        ActionJackson.ran_action = :access_protected_method
+        ActionJackson.succeeded = foo_protected
+      end
+      action :access_private_method do
+        ActionJackson.ran_action = :access_private_method
+        ActionJackson.succeeded = foo_private
+      end
+      action :access_instance_variable do
+        ActionJackson.ran_action = :access_instance_variable
+        ActionJackson.succeeded = @foo
+      end
+      action :access_class_method do
+        ActionJackson.ran_action = :access_class_method
+        ActionJackson.succeeded = ActionJackson.ruby_block_converged
+      end
+    end
+
+    before(:each) {
+      ActionJackson.ran_action = :error
+      ActionJackson.succeeded = :error
+      ActionJackson.ruby_block_converged = :error
+    }
+
+    it_behaves_like "ActionJackson" do
+      let(:resource_dsl) { :action_jackson }
+    end
+
+    it "Can retrieve ancestors of action class without crashing" do
+      converge { action_jackson 'hi' }
+      expect { ActionJackson.action_class.ancestors.join(",") }.not_to raise_error
+    end
+
+    context "And 'action_jackgrandson' inheriting from ActionJackson and changing nothing" do
+      before(:context) {
+        class ActionJackgrandson < ActionJackson
+          use_automatic_resource_name
+        end
+      }
+
+      it_behaves_like "ActionJackson" do
+        let(:resource_dsl) { :action_jackgrandson }
+      end
+    end
+
+    context "And 'action_jackalope' inheriting from ActionJackson with an extra attribute, action and custom method" do
+      class ActionJackalope < ActionJackson
+        use_automatic_resource_name
+
+        def foo(value=nil)
+          @foo = "#{value}alope" if value
+          @foo
+        end
+        def bar(value=nil)
+          @bar = "#{value}alope" if value
+          @bar
+        end
+        class <<self
+          attr_accessor :load_current_resource_ran
+          attr_accessor :jackalope_ran
+        end
+        action :access_jackalope do
+          ActionJackalope.jackalope_ran = :access_jackalope
+          ActionJackalope.succeeded = "#{foo} #{blarghle} #{bar}"
+        end
+        action :access_attribute do
+          super()
+          ActionJackalope.jackalope_ran = :access_attribute
+          ActionJackalope.succeeded = ActionJackson.succeeded
+        end
+      end
+      before do
+        ActionJackalope.jackalope_ran = nil
+        ActionJackalope.load_current_resource_ran = nil
+      end
+
+      context "action_jackson still behaves the same" do
+        it_behaves_like "ActionJackson" do
+          let(:resource_dsl) { :action_jackson }
+        end
+      end
+
+      it "the default action remains the same even though new actions were specified first" do
+        converge {
+          action_jackalope 'hi' do
+            foo 'foo!'
+            bar 'bar!'
+          end
+        }
+        expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+        expect(ActionJackson.succeeded).to eq true
+      end
+
+      it "new actions run, and can access overridden, new, and overridden attributes" do
+        converge {
+          action_jackalope 'hi' do
+            foo 'foo!'
+            bar 'bar!'
+            blarghle 'blarghle!'
+            action :access_jackalope
+          end
+        }
+        expect(ActionJackalope.jackalope_ran).to eq :access_jackalope
+        expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
+      end
+
+      it "overridden actions run, call super, and can access overridden, new, and overridden attributes" do
+        converge {
+          action_jackalope 'hi' do
+            foo 'foo!'
+            bar 'bar!'
+            blarghle 'blarghle!'
+            action :access_attribute
+          end
+        }
+        expect(ActionJackson.ran_action).to eq :access_attribute
+        expect(ActionJackson.succeeded).to eq "foo!alope blarghle! bar!alope"
+        expect(ActionJackalope.jackalope_ran).to eq :access_attribute
+        expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
+      end
+
+      it "non-overridden actions run and can access overridden and non-overridden variables (but not necessarily new ones)" do
+        converge {
+          action_jackalope 'hi' do
+            foo 'foo!'
+            bar 'bar!'
+            blarghle 'blarghle!'
+            action :access_attribute2
+          end
+        }
+        expect(ActionJackson.ran_action).to eq :access_attribute2
+        expect(ActionJackson.succeeded).to eq("foo!alope blarghle! bar!alope").or(eq("foo!alope blarghle!"))
+      end
+    end
+  end
+
+  context "With a resource with no actions" do
+    class NoActionJackson < Chef::Resource
+      use_automatic_resource_name
+
+      def foo(value=nil)
+        @foo = value if value
+        @foo
+      end
+
+      class <<self
+        attr_accessor :action_was
+      end
+    end
+
+    it "the default action is :nothing" do
+      converge {
+        no_action_jackson 'hi' do
+          foo 'foo!'
+          NoActionJackson.action_was = action
+        end
+      }
+      expect(NoActionJackson.action_was).to eq [:nothing]
+    end
+  end
+
+  context "With a resource with action a-b-c d" do
+    class WeirdActionJackson < Chef::Resource
+      use_automatic_resource_name
+
+      class <<self
+        attr_accessor :action_was
+      end
+
+      action "a-b-c d" do
+        WeirdActionJackson.action_was = action
+      end
+    end
+
+    it "Running the action works" do
+      expect_recipe {
+        weird_action_jackson 'hi'
+      }.to be_up_to_date
+      expect(WeirdActionJackson.action_was).to eq :"a-b-c d"
+    end
+  end
+
+  context "With a resource with property x" do
+    class ResourceActionSpecWithX < Chef::Resource
+      resource_name :resource_action_spec_with_x
+      property :x, default: 20
+      action :set do
+        # Access x during converge to ensure that we emit no warnings there
+        x
+      end
+    end
+
+    context "And another resource with a property x and an action that sets property x to its value" do
+      class ResourceActionSpecAlsoWithX < Chef::Resource
+        resource_name :resource_action_spec_also_with_x
+        property :x
+        action :set_x_to_x do
+          resource_action_spec_with_x 'hi' do
+            x x
+          end
+        end
+        def self.x_warning_line
+          __LINE__-4
+        end
+        action :set_x_to_x_in_non_initializer do
+          r = resource_action_spec_with_x 'hi' do
+            x 10
+          end
+          x_times_2 = r.x*2
+        end
+        action :set_x_to_10 do
+          resource_action_spec_with_x 'hi' do
+            x 10
+          end
+        end
+      end
+
+      attr_reader :x_warning_line
+
+      it "Using the enclosing resource to set x to x emits a warning that you're using the wrong x" do
+        recipe = converge {
+          resource_action_spec_also_with_x 'hi' do
+            x 1
+            action :set_x_to_x
+          end
+        }
+        warnings = recipe.logs.lines.select { |l| l =~ /warn/i }
+        expect(warnings.size).to eq 1
+        expect(warnings[0]).to match(/property x is declared in both resource_action_spec_with_x\[hi\] and resource_action_spec_also_with_x\[hi\] action :set_x_to_x. Use new_resource.x instead. At #{__FILE__}:#{ResourceActionSpecAlsoWithX.x_warning_line}/)
+      end
+
+      it "Using the enclosing resource to set x to x outside the initializer emits no warning" do
+        expect_recipe {
+          resource_action_spec_also_with_x 'hi' do
+            x 1
+            action :set_x_to_x_in_non_initializer
+          end
+        }.to emit_no_warnings_or_errors
+      end
+
+      it "Using the enclosing resource to set x to 10 emits no warning" do
+        expect_recipe {
+          resource_action_spec_also_with_x 'hi' do
+            x 1
+            action :set_x_to_10
+          end
+        }.to emit_no_warnings_or_errors
+      end
+
+      it "Using the enclosing resource to set x to 10 emits no warning" do
+        expect_recipe {
+          r = resource_action_spec_also_with_x 'hi'
+          r.x 1
+          r.action :set_x_to_10
+        }.to emit_no_warnings_or_errors
+      end
+    end
+
+  end
+
+  context "When a resource has a property with the same name as another resource" do
+    class HasPropertyNamedTemplate < Chef::Resource
+      use_automatic_resource_name
+      property :template
+      action :create do
+        template "x" do
+          'blah'
+        end
+      end
+    end
+
+    it "Raises an error when attempting to use a template in the action" do
+      expect_converge {
+        has_property_named_template 'hi'
+      }.to raise_error(/Property template of has_property_named_template\[hi\] cannot be passed a block! If you meant to create a resource named template instead, you'll need to first rename the property./)
+    end
+  end
+end
+
+end
diff --git a/spec/integration/recipes/resource_converge_if_changed_spec.rb b/spec/integration/recipes/resource_converge_if_changed_spec.rb
new file mode 100644
index 0000000..f3b562c
--- /dev/null
+++ b/spec/integration/recipes/resource_converge_if_changed_spec.rb
@@ -0,0 +1,495 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Resource::ActionClass#converge_if_changed" do
+  include IntegrationSupport
+
+  module Namer
+    extend self
+    attr_accessor :current_index
+    def incrementing_value
+      @incrementing_value += 1
+      @incrementing_value
+    end
+    attr_writer :incrementing_value
+  end
+
+  before(:all) { Namer.current_index = 1 }
+  before { Namer.current_index += 1 }
+  before { Namer.incrementing_value = 0 }
+
+  context "when the resource has identity, state and control properties" do
+    let(:resource_name) { :"converge_if_changed_dsl#{Namer.current_index}" }
+    let(:resource_class) {
+      result = Class.new(Chef::Resource) do
+        def self.to_s; resource_name; end
+        def self.inspect; resource_name.inspect; end
+        property :identity1, identity: true, default: 'default_identity1'
+        property :control1, desired_state: false, default: 'default_control1'
+        property :state1, default: 'default_state1'
+        property :state2, default: 'default_state2'
+        attr_accessor :converged
+        def initialize(*args)
+          super
+          @converged = 0
+        end
+      end
+      result.resource_name resource_name
+      result
+    }
+    let(:converged_recipe) { converge(converge_recipe) }
+    let(:resource) { converged_recipe.resources.first }
+
+    context "and converge_if_changed with no parameters" do
+      before :each do
+        resource_class.action :create do
+          converge_if_changed do
+            new_resource.converged += 1
+          end
+        end
+      end
+
+      context "and current_resource with state1=current, state2=current" do
+        before :each do
+          resource_class.load_current_value do
+            state1 'current_state1'
+            state2 'current_state2'
+          end
+        end
+
+        context "and nothing is set" do
+          let(:converge_recipe) { "#{resource_name} 'blah'" }
+
+          it "the resource updates nothing" do
+            expect(resource.converged).to eq 0
+            expect(resource.updated?).to  be_falsey
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+            EOM
+          end
+        end
+
+        context "and state1 is set to a new value" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'new_state1'
+              end
+            EOM
+          }
+
+          it "the resource updates state1" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - update default_identity1
+  -   set state1 to "new_state1" (was "current_state1")
+              EOM
+          end
+        end
+
+        context "and state1 and state2 are set to new values" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'new_state1'
+                state2 'new_state2'
+              end
+            EOM
+          }
+
+          it "the resource updates state1 and state2" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - update default_identity1
+  -   set state1 to "new_state1" (was "current_state1")
+  -   set state2 to "new_state2" (was "current_state2")
+EOM
+          end
+        end
+
+        context "and state1 and state2 are set to new sensitive values" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                sensitive true
+                state1 'new_state1'
+                state2 'new_state2'
+              end
+            EOM
+          }
+
+          it "the resource updates state1 and state2" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - update default_identity1
+  -   set state1 to (suppressed sensitive property)
+  -   set state2 to (suppressed sensitive property)
+EOM
+          end
+        end
+
+        context "and state1 is set to its current value but state2 is set to a new value" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'current_state1'
+                state2 'new_state2'
+              end
+            EOM
+          }
+
+          it "the resource updates state2" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - update default_identity1
+  -   set state2 to "new_state2" (was "current_state2")
+EOM
+          end
+        end
+
+        context "and state1 and state2 are set to their current values" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'current_state1'
+                state2 'current_state2'
+              end
+            EOM
+          }
+
+          it "the resource updates nothing" do
+            expect(resource.converged).to eq 0
+            expect(resource.updated?).to  be_falsey
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+EOM
+          end
+        end
+
+        context "and identity1 and control1 are set to new values" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                identity1 'new_identity1'
+                control1 'new_control1'
+              end
+            EOM
+          }
+
+          # Because the identity value is copied over to the new resource, by
+          # default they do not register as "changed"
+          it "the resource updates nothing" do
+            expect(resource.converged).to eq 0
+            expect(resource.updated?).to  be_falsey
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+EOM
+          end
+        end
+      end
+
+      context "and current_resource with identity1=current, control1=current" do
+        before :each do
+          resource_class.load_current_value do
+            identity1 'current_identity1'
+            control1 'current_control1'
+          end
+        end
+
+        context "and identity1 and control1 are set to new values" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                identity1 'new_identity1'
+                control1 'new_control1'
+              end
+            EOM
+          }
+
+          # Control values are not desired state and are therefore not considered
+          # a reason for converging.
+          it "the resource updates identity1" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - update current_identity1
+  -   set identity1 to "new_identity1" (was "current_identity1")
+            EOM
+          end
+        end
+      end
+
+      context "and has no current_resource" do
+        before :each do
+          resource_class.load_current_value do
+            current_value_does_not_exist!
+          end
+        end
+
+        context "and nothing is set" do
+          let(:converge_recipe) { "#{resource_name} 'blah'" }
+
+          it "the resource is created" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - create default_identity1
+  -   set identity1 to "default_identity1" (default value)
+  -   set state1    to "default_state1" (default value)
+  -   set state2    to "default_state2" (default value)
+EOM
+          end
+        end
+
+        context "and state1 and state2 are set" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'new_state1'
+                state2 'new_state2'
+              end
+            EOM
+          }
+
+          it "the resource is created" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - create default_identity1
+  -   set identity1 to "default_identity1" (default value)
+  -   set state1    to "new_state1"
+  -   set state2    to "new_state2"
+EOM
+          end
+        end
+
+        context "and state1 and state2 are set with sensitive property" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                sensitive true
+                state1 'new_state1'
+                state2 'new_state2'
+              end
+            EOM
+          }
+
+          it "the resource is created" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - create default_identity1
+  -   set identity1 to (suppressed sensitive property) (default value)
+  -   set state1    to (suppressed sensitive property)
+  -   set state2    to (suppressed sensitive property)
+EOM
+          end
+        end
+      end
+    end
+
+    context "and separate converge_if_changed :state1 and converge_if_changed :state2" do
+      before :each do
+        resource_class.action :create do
+          converge_if_changed :state1 do
+            new_resource.converged += 1
+          end
+          converge_if_changed :state2 do
+            new_resource.converged += 1
+          end
+        end
+      end
+
+      context "and current_resource with state1=current, state2=current" do
+        before :each do
+          resource_class.load_current_value do
+            state1 'current_state1'
+            state2 'current_state2'
+          end
+        end
+
+        context "and nothing is set" do
+          let(:converge_recipe) { "#{resource_name} 'blah'" }
+
+          it "the resource updates nothing" do
+            expect(resource.converged).to eq 0
+            expect(resource.updated?).to  be_falsey
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+EOM
+          end
+        end
+
+        context "and state1 is set to a new value" do
+
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'new_state1'
+              end
+            EOM
+          }
+
+          it "the resource updates state1" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - update default_identity1
+  -   set state1 to "new_state1" (was "current_state1")
+EOM
+          end
+        end
+
+        context "and state1 and state2 are set to new values" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'new_state1'
+                state2 'new_state2'
+              end
+            EOM
+          }
+
+          it "the resource updates state1 and state2" do
+            expect(resource.converged).to eq 2
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - update default_identity1
+  -   set state1 to "new_state1" (was "current_state1")
+  - update default_identity1
+  -   set state2 to "new_state2" (was "current_state2")
+EOM
+          end
+        end
+
+        context "and state1 is set to its current value but state2 is set to a new value" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'current_state1'
+                state2 'new_state2'
+              end
+            EOM
+          }
+
+          it "the resource updates state2" do
+            expect(resource.converged).to eq 1
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - update default_identity1
+  -   set state2 to "new_state2" (was "current_state2")
+EOM
+          end
+        end
+
+        context "and state1 and state2 are set to their current values" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'current_state1'
+                state2 'current_state2'
+              end
+            EOM
+          }
+
+          it "the resource updates nothing" do
+            expect(resource.converged).to eq 0
+            expect(resource.updated?).to  be_falsey
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+EOM
+          end
+        end
+      end
+
+      context "and no current_resource" do
+        before :each do
+          resource_class.load_current_value do
+            current_value_does_not_exist!
+          end
+        end
+
+        context "and nothing is set" do
+          let(:converge_recipe) {
+            "#{resource_name} 'blah'"
+          }
+
+          it "the resource is created" do
+            expect(resource.converged).to eq 2
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - create default_identity1
+  -   set state1 to "default_state1" (default value)
+  - create default_identity1
+  -   set state2 to "default_state2" (default value)
+EOM
+          end
+        end
+
+        context "and state1 and state2 are set to new values" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                state1 'new_state1'
+                state2 'new_state2'
+              end
+            EOM
+          }
+
+          it "the resource is created" do
+            expect(resource.converged).to eq 2
+            expect(resource.updated?).to  be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - create default_identity1
+  -   set state1 to "new_state1"
+  - create default_identity1
+  -   set state2 to "new_state2"
+EOM
+          end
+        end
+
+        context "and state1 and state2 are set to new sensitive values" do
+          let(:converge_recipe) {
+            <<-EOM
+              #{resource_name} 'blah' do
+                sensitive true
+                state1 'new_state1'
+                state2 'new_state2'
+              end
+            EOM
+          }
+
+          it "the resource is created" do
+            expect(resource.converged).to eq 2
+            expect(resource.updated?).to be_truthy
+            expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+  - create default_identity1
+  -   set state1 to (suppressed sensitive property)
+  - create default_identity1
+  -   set state2 to (suppressed sensitive property)
+EOM
+          end
+        end
+
+      end
+    end
+
+  end
+end
diff --git a/spec/integration/recipes/resource_load_spec.rb b/spec/integration/recipes/resource_load_spec.rb
new file mode 100644
index 0000000..556201e
--- /dev/null
+++ b/spec/integration/recipes/resource_load_spec.rb
@@ -0,0 +1,206 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Resource.load_current_value" do
+  include IntegrationSupport
+
+  module Namer
+    extend self
+    attr_accessor :current_index
+    def incrementing_value
+      @incrementing_value += 1
+      @incrementing_value
+    end
+    attr_writer :incrementing_value
+  end
+
+  before(:all) { Namer.current_index = 1 }
+  before { Namer.current_index += 1 }
+  before { Namer.incrementing_value = 0 }
+
+  let(:resource_name) { :"load_current_value_dsl#{Namer.current_index}" }
+  let(:resource_class) {
+    result = Class.new(Chef::Resource) do
+      def self.to_s; resource_name; end
+      def self.inspect; resource_name.inspect; end
+      property :x, default: lazy { "default #{Namer.incrementing_value}" }
+      def self.created_x=(value)
+        @created = value
+      end
+      def self.created_x
+        @created
+      end
+      action :create do
+        new_resource.class.created_x = x
+      end
+    end
+    result.resource_name resource_name
+    result
+  }
+
+  # Pull on resource_class to initialize it
+  before { resource_class }
+
+  context "with a resource with load_current_value" do
+    before :each do
+      resource_class.load_current_value do
+        x "loaded #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name,p| name }.
+          select { |name,p| p.is_set?(self) }.
+          map { |name,p| "#{name}=#{p.get(self)}" }.
+          join(", ") })"
+      end
+    end
+
+    context "and a resource with x set to a desired value" do
+      let(:resource) do
+        e = self
+        r = nil
+        converge {
+          r = public_send(e.resource_name, 'blah') do
+            x 'desired'
+          end
+        }
+        r
+      end
+
+      it "current_resource is passed name but not x" do
+        expect(resource.current_value.x).to eq 'loaded 2 (name=blah)'
+      end
+
+      it "resource.current_value returns a different resource" do
+        expect(resource.current_value.x).to eq 'loaded 2 (name=blah)'
+        expect(resource.x).to eq 'desired'
+      end
+
+      it "resource.current_value constructs the resource anew each time" do
+        expect(resource.current_value.x).to eq 'loaded 2 (name=blah)'
+        expect(resource.current_value.x).to eq 'loaded 3 (name=blah)'
+      end
+
+      it "the provider accesses the current value of x" do
+        expect(resource.class.created_x).to eq 'desired'
+      end
+
+      context "and identity: :i and :d with desired_state: false" do
+        before {
+          resource_class.class_eval do
+            property :i, identity: true
+            property :d, desired_state: false
+          end
+        }
+
+        before {
+          resource.i 'desired_i'
+          resource.d 'desired_d'
+        }
+
+        it "i, name and d are passed to load_current_value, but not x" do
+          expect(resource.current_value.x).to eq 'loaded 2 (d=desired_d, i=desired_i, name=blah)'
+        end
+      end
+
+      context "and name_property: :i and :d with desired_state: false" do
+        before {
+          resource_class.class_eval do
+            property :i, name_property: true
+            property :d, desired_state: false
+          end
+        }
+
+        before {
+          resource.i 'desired_i'
+          resource.d 'desired_d'
+        }
+
+        it "i, name and d are passed to load_current_value, but not x" do
+          expect(resource.current_value.x).to eq 'loaded 2 (d=desired_d, i=desired_i, name=blah)'
+        end
+      end
+    end
+
+    context "and a resource with no values set" do
+      let(:resource) do
+        e = self
+        r = nil
+        converge {
+          r = public_send(e.resource_name, 'blah') do
+          end
+        }
+        r
+      end
+
+      it "the provider accesses values from load_current_value" do
+        expect(resource.class.created_x).to eq 'loaded 1 (name=blah)'
+      end
+    end
+
+    let (:subresource_name) {
+      :"load_current_value_subresource_dsl#{Namer.current_index}"
+    }
+    let (:subresource_class) {
+      r = Class.new(resource_class) do
+        property :y, default: lazy { "default_y #{Namer.incrementing_value}" }
+      end
+      r.resource_name subresource_name
+      r
+    }
+
+    # Pull on subresource_class to initialize it
+    before { subresource_class }
+
+    let(:subresource) do
+      e = self
+      r = nil
+      converge {
+        r = public_send(e.subresource_name, 'blah') do
+          x 'desired'
+        end
+      }
+      r
+    end
+
+    context "and a child resource class with no load_current_value" do
+      it "the parent load_current_value is used" do
+        expect(subresource.current_value.x).to eq 'loaded 2 (name=blah)'
+      end
+      it "load_current_value yields a copy of the child class" do
+        expect(subresource.current_value).to be_kind_of(subresource_class)
+      end
+    end
+
+    context "And a child resource class with load_current_value" do
+      before {
+        subresource_class.load_current_value do
+          y "loaded_y #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name,p| name }.
+            select { |name,p| p.is_set?(self) }.
+            map { |name,p| "#{name}=#{p.get(self)}" }.
+            join(", ") })"
+        end
+      }
+
+      it "the overridden load_current_value is used" do
+        current_resource = subresource.current_value
+        expect(current_resource.x).to eq 'default 3'
+        expect(current_resource.y).to eq 'loaded_y 2 (name=blah)'
+      end
+    end
+
+    context "and a child resource class with load_current_value calling super()" do
+      before {
+        subresource_class.load_current_value do
+          super()
+          y "loaded_y #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name,p| name }.
+            select { |name,p| p.is_set?(self) }.
+            map { |name,p| "#{name}=#{p.get(self)}" }.
+            join(", ") })"
+        end
+      }
+
+      it "the original load_current_value is called as well as the child one" do
+        current_resource = subresource.current_value
+        expect(current_resource.x).to eq 'loaded 3 (name=blah)'
+        expect(current_resource.y).to eq 'loaded_y 4 (name=blah, x=loaded 3 (name=blah))'
+      end
+    end
+  end
+
+end
diff --git a/spec/integration/solo/solo_spec.rb b/spec/integration/solo/solo_spec.rb
index f45933c..6240a38 100644
--- a/spec/integration/solo/solo_spec.rb
+++ b/spec/integration/solo/solo_spec.rb
@@ -70,6 +70,40 @@ EOM
     end
   end
 
+  when_the_repository "has a cookbook with an incompatible chef_version" do
+    before do
+      file 'cookbooks/x/metadata.rb', cb_metadata('x', '1.0.0', "\nchef_version '~> 999.0'")
+      file 'cookbooks/x/recipes/default.rb', 'puts "ITWORKS"'
+      file 'config/solo.rb', <<EOM
+cookbook_path "#{path_to('cookbooks')}"
+file_cache_path "#{path_to('config/cache')}"
+EOM
+    end
+
+    it "should exit with an error" do
+      result = shell_out("#{chef_solo} -c \"#{path_to('config/solo.rb')}\" -o 'x::default' -l debug", :cwd => chef_dir)
+      expect(result.exitstatus).to eq(1)
+      expect(result.stdout).to include("Chef::Exceptions::CookbookChefVersionMismatch")
+    end
+  end
+
+  when_the_repository "has a cookbook with an incompatible ohai_version" do
+    before do
+      file 'cookbooks/x/metadata.rb', cb_metadata('x', '1.0.0', "\nohai_version '~> 999.0'")
+      file 'cookbooks/x/recipes/default.rb', 'puts "ITWORKS"'
+      file 'config/solo.rb', <<EOM
+cookbook_path "#{path_to('cookbooks')}"
+file_cache_path "#{path_to('config/cache')}"
+EOM
+    end
+
+    it "should exit with an error" do
+      result = shell_out("#{chef_solo} -c \"#{path_to('config/solo.rb')}\" -o 'x::default' -l debug", :cwd => chef_dir)
+      expect(result.exitstatus).to eq(1)
+      expect(result.stdout).to include("Chef::Exceptions::CookbookOhaiVersionMismatch")
+    end
+  end
+
 
   when_the_repository "has a cookbook with a recipe with sleep" do
     before do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index fb284c7..a269a98 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -54,6 +54,9 @@ Dir['lib/chef/knife/**/*.rb'].
   map {|f| f.gsub(%r[\.rb$], '') }.
   each {|f| require f }
 
+require 'chef/resource_resolver'
+require 'chef/provider_resolver'
+
 require 'chef/mixins'
 require 'chef/dsl'
 require 'chef/application'
@@ -70,6 +73,7 @@ require 'spec/support/local_gems.rb' if File.exists?(File.join(File.dirname(__FI
 
 # Explicitly require spec helpers that need to load first
 require 'spec/support/platform_helpers'
+require 'spec/support/shared/unit/mock_shellout'
 
 # Autoloads support files
 # Excludes support/platforms by default
@@ -84,15 +88,20 @@ Dir["spec/support/**/*.rb"].
 OHAI_SYSTEM = Ohai::System.new
 OHAI_SYSTEM.all_plugins("platform")
 
-TEST_PLATFORM =
-  (OHAI_SYSTEM['platform'] ||
-  'unknown_test_platform').dup.freeze
-TEST_PLATFORM_VERSION =
-  (OHAI_SYSTEM['platform_version'] ||
-  'unknown_platform_version').dup.freeze
+test_node = Chef::Node.new
+test_node.automatic['os'] = (OHAI_SYSTEM['os'] || 'unknown_os').dup.freeze
+test_node.automatic['platform_family'] = (OHAI_SYSTEM['platform_family'] || 'unknown_platform_family').dup.freeze
+test_node.automatic['platform'] = (OHAI_SYSTEM['platform'] || 'unknown_platform').dup.freeze
+test_node.automatic['platform_version'] = (OHAI_SYSTEM['platform_version'] || 'unknown_platform_version').dup.freeze
+TEST_NODE = test_node.freeze
+TEST_OS = TEST_NODE['os']
+TEST_PLATFORM = TEST_NODE['platform']
+TEST_PLATFORM_VERSION = TEST_NODE['platform_version']
+TEST_PLATFORM_FAMILY = TEST_NODE['platform_family']
 
 RSpec.configure do |config|
   config.include(Matchers)
+  config.include(MockShellout::RSpec)
   config.filter_run :focus => true
   config.filter_run_excluding :external => true
 
@@ -112,24 +121,33 @@ RSpec.configure do |config|
   config.filter_run_excluding :volatile_on_solaris => true if solaris?
   config.filter_run_excluding :volatile_from_verify => false
 
-  # Add jruby filters here
+  config.filter_run_excluding :skip_appveyor => true if ENV["APPVEYOR"]
+  config.filter_run_excluding :appveyor_only => true unless ENV["APPVEYOR"]
+  config.filter_run_excluding :skip_travis => true if ENV["TRAVIS"]
+
   config.filter_run_excluding :windows_only => true unless windows?
   config.filter_run_excluding :not_supported_on_mac_osx_106 => true if mac_osx_106?
   config.filter_run_excluding :not_supported_on_mac_osx=> true if mac_osx?
   config.filter_run_excluding :mac_osx_only=> true if !mac_osx?
   config.filter_run_excluding :not_supported_on_win2k3 => true if windows_win2k3?
   config.filter_run_excluding :not_supported_on_solaris => true if solaris?
+  config.filter_run_excluding :not_supported_on_nano => true if windows_nano_server?
   config.filter_run_excluding :win2k3_only => true unless windows_win2k3?
   config.filter_run_excluding :windows_2008r2_or_later => true unless windows_2008r2_or_later?
   config.filter_run_excluding :windows64_only => true unless windows64?
   config.filter_run_excluding :windows32_only => true unless windows32?
+  config.filter_run_excluding :windows_nano_only => true unless windows_nano_server?
+  config.filter_run_excluding :ruby64_only => true unless ruby_64bit?
+  config.filter_run_excluding :ruby32_only => true unless ruby_32bit?
   config.filter_run_excluding :windows_powershell_dsc_only => true unless windows_powershell_dsc?
   config.filter_run_excluding :windows_powershell_no_dsc_only => true unless ! windows_powershell_dsc?
   config.filter_run_excluding :windows_domain_joined_only => true unless windows_domain_joined?
+  config.filter_run_excluding :windows_not_domain_joined_only => true if windows_domain_joined?
   config.filter_run_excluding :solaris_only => true unless solaris?
   config.filter_run_excluding :system_windows_service_gem_only => true unless system_windows_service_gem?
   config.filter_run_excluding :unix_only => true unless unix?
   config.filter_run_excluding :aix_only => true unless aix?
+  config.filter_run_excluding :debian_family_only => true unless debian_family?
   config.filter_run_excluding :supports_cloexec => true unless supports_cloexec?
   config.filter_run_excluding :selinux_only => true unless selinux_enabled?
   config.filter_run_excluding :ruby_20_only => true unless ruby_20?
@@ -145,8 +163,9 @@ RSpec.configure do |config|
   config.filter_run_excluding :openssl_lt_101 => true unless openssl_lt_101?
   config.filter_run_excluding :aes_256_gcm_only => true unless aes_256_gcm?
   config.filter_run_excluding :broken => true
+  config.filter_run_excluding :not_wpar => true unless wpar?
 
-  running_platform_arch = `uname -m`.strip
+  running_platform_arch = `uname -m`.strip unless windows?
 
   config.filter_run_excluding :arch => lambda {|target_arch|
     running_platform_arch != target_arch
@@ -157,13 +176,17 @@ RSpec.configure do |config|
   config.filter_run_excluding :provider => lambda {|criteria|
     type, target_provider = criteria.first
 
-    platform = TEST_PLATFORM.dup
-    platform_version = TEST_PLATFORM_VERSION.dup
-
-    begin
-      provider_for_running_platform = Chef::Platform.find_provider(platform, platform_version, type)
-      provider_for_running_platform != target_provider
-    rescue ArgumentError # no provider for platform
+    node = TEST_NODE.dup
+    resource_class = Chef::ResourceResolver.resolve(type, node: node)
+    if resource_class
+      resource = resource_class.new('test', Chef::RunContext.new(node, nil, nil))
+      begin
+        provider = resource.provider_for_action(Array(resource_class.default_action).first)
+        provider.class != target_provider
+      rescue Chef::Exceptions::ProviderNotFound # no provider for platform
+        true
+      end
+    else
       true
     end
   }
@@ -171,6 +194,8 @@ RSpec.configure do |config|
   config.run_all_when_everything_filtered = true
 
   config.before(:each) do
+    Chef.reset!
+
     Chef::Config.reset
 
     # By default, treat deprecation warnings as errors in tests.
@@ -180,6 +205,11 @@ RSpec.configure do |config|
     ENV['CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS'] = "1"
   end
 
+  # raise if anyone commits any test to CI with :focus set on it
+  config.before(:example, :focus) do
+    raise 'This example was committed with `:focus` and should not have been'
+  end if ENV['CI']
+
   config.before(:suite) do
     ARGV.clear
   end
diff --git a/spec/support/key_helpers.rb b/spec/support/key_helpers.rb
new file mode 100644
index 0000000..076f709
--- /dev/null
+++ b/spec/support/key_helpers.rb
@@ -0,0 +1,104 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+shared_examples_for "a knife key command" do
+  let(:stderr) { StringIO.new }
+  let(:command) do
+    c = described_class.new([])
+    c.ui.config[:disable_editing] = true
+    allow(c.ui).to receive(:stderr).and_return(stderr)
+    allow(c.ui).to receive(:stdout).and_return(stderr)
+    allow(c).to receive(:show_usage)
+    c
+  end
+
+  context "before apply_params! is called" do
+    context "when apply_params! is called with invalid args (missing actor)" do
+      let(:params) { [] }
+      it "shows the usage" do
+        expect(command).to receive(:show_usage)
+        expect { command.apply_params!(params) }.to exit_with_code(1)
+      end
+
+      it "outputs the proper error" do
+        expect { command.apply_params!(params) }.to exit_with_code(1)
+        expect(stderr.string).to include(command.actor_missing_error)
+      end
+
+      it "exits 1" do
+        expect { command.apply_params!(params) }.to exit_with_code(1)
+      end
+    end
+  end # before apply_params! is called
+
+  context "after apply_params! is called with valid args" do
+    before do
+      command.apply_params!(params)
+    end
+
+    it "properly defines the actor" do
+      expect(command.actor).to eq("charmander")
+    end
+  end # after apply_params! is called with valid args
+
+  context "when the command is run" do
+    before do
+      allow(command).to receive(:service_object).and_return(service_object)
+      allow(command).to receive(:name_args).and_return(["charmander"])
+    end
+
+    context "when the command is successful" do
+      before do
+        expect(service_object).to receive(:run)
+      end
+    end
+  end
+end # a knife key command
+
+shared_examples_for "a knife key command with a keyname as the second arg" do
+  let(:stderr) { StringIO.new }
+  let(:command) do
+    c = described_class.new([])
+    c.ui.config[:disable_editing] = true
+    allow(c.ui).to receive(:stderr).and_return(stderr)
+    allow(c.ui).to receive(:stdout).and_return(stderr)
+    allow(c).to receive(:show_usage)
+    c
+  end
+
+  context "before apply_params! is called" do
+    context "when apply_params! is called with invalid args (missing keyname)" do
+      let(:params) { ["charmander"] }
+      it "shows the usage" do
+        expect(command).to receive(:show_usage)
+        expect { command.apply_params!(params) }.to exit_with_code(1)
+      end
+
+      it "outputs the proper error" do
+        expect { command.apply_params!(params) }.to exit_with_code(1)
+        expect(stderr.string).to include(command.keyname_missing_error)
+      end
+
+      it "exits 1" do
+        expect { command.apply_params!(params) }.to exit_with_code(1)
+      end
+    end
+  end # before apply_params! is called
+end
diff --git a/lib/chef/resource/csh.rb b/spec/support/lib/chef/provider/openldap_includer.rb
similarity index 75%
copy from lib/chef/resource/csh.rb
copy to spec/support/lib/chef/provider/openldap_includer.rb
index 36659c3..afb0c7c 100644
--- a/lib/chef/resource/csh.rb
+++ b/spec/support/lib/chef/provider/openldap_includer.rb
@@ -16,19 +16,14 @@
 # limitations under the License.
 #
 
-require 'chef/resource/script'
-require 'chef/provider/script'
-
 class Chef
-  class Resource
-    class Csh < Chef::Resource::Script
+  class Provider
+    class OpenldapIncluder < Chef::Provider::LWRPBase
+      provides :openldap_includer
 
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :csh
-        @interpreter = "csh"
+      def action_run
+        include_recipe "openldap::default"
       end
-
     end
   end
 end
diff --git a/spec/support/lib/chef/resource/cat.rb b/spec/support/lib/chef/resource/cat.rb
index ecca50c..efc78aa 100644
--- a/spec/support/lib/chef/resource/cat.rb
+++ b/spec/support/lib/chef/resource/cat.rb
@@ -23,7 +23,6 @@ class Chef
       attr_accessor :action
 
       def initialize(name, run_context=nil)
-        @resource_name = :cat
         super
         @action = "sell"
       end
diff --git a/spec/support/lib/chef/resource/one_two_three_four.rb b/spec/support/lib/chef/resource/one_two_three_four.rb
index 296d2cd..8f273a0 100644
--- a/spec/support/lib/chef/resource/one_two_three_four.rb
+++ b/spec/support/lib/chef/resource/one_two_three_four.rb
@@ -19,12 +19,8 @@
 class Chef
   class Resource
     class OneTwoThreeFour < Chef::Resource
-      attr_reader :i_can_count
 
-      def initialize(name, run_context)
-        @resource_name = :one_two_three_four
-        super
-      end
+      attr_reader :i_can_count
 
       def i_can_count(tf)
         @i_can_count = tf
diff --git a/spec/support/lib/chef/resource/with_state.rb b/spec/support/lib/chef/resource/openldap_includer.rb
similarity index 73%
copy from spec/support/lib/chef/resource/with_state.rb
copy to spec/support/lib/chef/resource/openldap_includer.rb
index 226de0a..6f443b4 100644
--- a/spec/support/lib/chef/resource/with_state.rb
+++ b/spec/support/lib/chef/resource/openldap_includer.rb
@@ -16,22 +16,12 @@
 # limitations under the License.
 #
 
-require 'chef/knife'
-require 'chef/json_compat'
 
 class Chef
   class Resource
-    class WithState < Chef::Resource
-      attr_accessor :state
-
-      def initialize(name, run_context=nil)
-        @resource_name = :with_state
-        super
-      end
-
-      def state
-        @state
-      end
+    class OpenldapIncluder < Chef::Resource::LWRPBase
+      allowed_actions :run
+      default_action :run
     end
   end
 end
diff --git a/spec/support/lib/chef/resource/with_state.rb b/spec/support/lib/chef/resource/with_state.rb
index 226de0a..773ae7d 100644
--- a/spec/support/lib/chef/resource/with_state.rb
+++ b/spec/support/lib/chef/resource/with_state.rb
@@ -23,15 +23,6 @@ class Chef
   class Resource
     class WithState < Chef::Resource
       attr_accessor :state
-
-      def initialize(name, run_context=nil)
-        @resource_name = :with_state
-        super
-      end
-
-      def state
-        @state
-      end
     end
   end
 end
diff --git a/spec/support/lib/chef/resource/zen_follower.rb b/spec/support/lib/chef/resource/zen_follower.rb
index ddc289e..155e6ae 100644
--- a/spec/support/lib/chef/resource/zen_follower.rb
+++ b/spec/support/lib/chef/resource/zen_follower.rb
@@ -24,11 +24,6 @@ class Chef
 
       provides :follower, platform: "zen"
 
-      def initialize(name, run_context=nil)
-        @resource_name = :zen_follower
-        super
-      end
-
       def master(arg=nil)
         if !arg.nil?
           @master = arg
diff --git a/spec/support/lib/chef/resource/zen_master.rb b/spec/support/lib/chef/resource/zen_master.rb
index d47d174..4106549 100644
--- a/spec/support/lib/chef/resource/zen_master.rb
+++ b/spec/support/lib/chef/resource/zen_master.rb
@@ -22,13 +22,10 @@ require 'chef/json_compat'
 class Chef
   class Resource
     class ZenMaster < Chef::Resource
+      allowed_actions :win, :score
+
       attr_reader :peace
 
-      def initialize(name, run_context=nil)
-        @resource_name = :zen_master
-        super
-        allowed_actions << :win << :score
-      end
 
       def peace(tf)
         @peace = tf
diff --git a/spec/support/mock/platform.rb b/spec/support/mock/platform.rb
index ab2c19b..7eae82f 100644
--- a/spec/support/mock/platform.rb
+++ b/spec/support/mock/platform.rb
@@ -6,7 +6,7 @@
 # testing code that mixes in platform specific modules like +Chef::Mixin::Securable+
 # or +Chef::FileAccessControl+
 def platform_mock(platform = :unix, &block)
-  allow(Chef::Platform).to receive(:windows?).and_return(platform == :windows ? true : false)
+  allow(ChefConfig).to receive(:windows?).and_return(platform == :windows ? true : false)
   ENV['SYSTEMDRIVE'] = (platform == :windows ? 'C:' : nil)
 
   if platform == :windows
diff --git a/spec/support/pedant/Gemfile b/spec/support/pedant/Gemfile
deleted file mode 100644
index d4224cd..0000000
--- a/spec/support/pedant/Gemfile
+++ /dev/null
@@ -1,3 +0,0 @@
-source "https://rubygems.org"
-
-gem 'chef-pedant', :github => 'opscode/chef-pedant', :ref => "server-cli-option"
diff --git a/spec/support/pedant/pedant_config.rb b/spec/support/pedant/pedant_config.rb
deleted file mode 100644
index 3f8219f..0000000
--- a/spec/support/pedant/pedant_config.rb
+++ /dev/null
@@ -1,129 +0,0 @@
-# Copyright: Copyright (c) 2012 Opscode, 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.
-
-# This annotated Pedant configuration file details the various
-# configuration settings available to you.  It is separate from the
-# actual Pedant::Config class because not all settings have sane
-# defaults, and not all settings are appropriate in all settings.
-
-################################################################################
-# You MUST specify the address of the server the API requests will be
-# sent to.  Only specify protocol, hostname, and port.
-# NOTE this is assigned in run_pedant.rb, because it's possible 8889 will not be the port chosen.
-#chef_server 'http://127.0.0.1:8889'
-
-# If you are doing development testing, you can specify the address of
-# the Solr server.  The presence of this parameter will enable tests
-# to force commits to Solr, greatly decreasing the amount of time
-# needed for testing the search endpoint.  This is only an
-# optimization for development!  If you are testing a "live" Chef
-# Server, or otherwise do not have access to the Solr server from your
-# testing location, you should not specify a value for this parameter.
-# The tests will still run, albeit slower, as they will now need to
-# poll for a period to ensure they are querying committed results.
-#search_server "http://localhost:8983"
-
-# Related to the 'search_server' parameter, this specifies the maximum
-# amount of time (in seconds) that search endpoint requests should be
-# retried before giving up.  If not explicitly set, it will default to
-# 65 seconds; only set it if you know that your Solr commit interval
-# differs significantly from this.
-maximum_search_time 0
-
-# OSC sends erchef a host header with a port, so this option needs
-# # to be enabled for Pedant tests to work correctly
-explicit_port_url true
-
-# We're starting to break tests up into groups based on different
-# criteria.  The proper API tests (the results of which are viewable
-# to OPC customers) should be the only ones run by Pedant embedded in
-# OPC installs.  There are other specs that help us keep track of API
-# cruft that we want to come back and fix later; these shouldn't be
-# viewable to customers, but we should be able to run them in
-# development and CI environments.  If this parameter is missing or
-# explicitly `false` only the customer-friendly tests will be run.
-#
-# This is mainly here for documentation purposes, since the
-# command-line `opscode-pedant` utility ultimately determines this
-# value.
-include_internal false
-
-# Test users.  The five users specified below are required; their
-# names (:user, :non_org_user, etc.) are indicative of their role
-# within the tests.  All users must have a ':name' key.  If they have
-# a ':create_me' key, Pedant will create these users for you.  If you
-# are using pre-existing users, you must supply a ':key_file' key,
-# which should be the fully-qualified path /on the machine Pedant is
-# running on/ to a private key for that user.
-key = 'spec/support/pedant/stickywicket.pem'
-superuser_name 'admin'
-superuser_key key
-webui_key key
-
-# When we updated Chef to RSpec 3 there were gem conflicts with chef-pedant.
-# We removed chef as a chef-pedant gem dependency in pedant.gemfile, but this
-# caused chef-pedant to fail because it could not query for the chef version
-# on the box pedant is running on. X-Chef-Version isn't needed in server
-# requests for these tests, so we've disabled it.
-ingore_x_chef_version true
-
-# Set the platform_class
-platform_class Pedant::OpenSourcePlatform
-
-requestors({
-  :clients => {
-    # The the admin user, for the purposes of getting things rolling
-    :admin => {
-      :name => "pedant_admin_client",
-      :create_me => true,
-      :create_knife => true,
-      :admin => true
-    },
-    :non_admin => {
-      :name => 'pedant_client',
-      :create_me => true,
-      :create_knife => true
-    },
-    :bad => {
-      :name => 'bad_client',
-      :bogus => true
-    }
-  },
-  :users => {
-    :admin => {
-      :name => "admin",
-      :key_file => key,
-      :create_me => false,
-      :create_knife => false,
-      :admin => true
-     },
-    :non_admin => {
-      :name => "pedant_non_admin_user",
-      :create_me => true,
-      :create_knife => true,
-      :admin => false
-    },
-    # A user for Knife tests.  A knife.rb and key files will be set up
-    # for this user
-    :knife_user => {
-      :name => "knifey",
-      :create_me => true,
-      :create_knife => true
-    }
-  }
-})
-
-self[:tags] = [:validation, :authentication, :authorization]
-verify_error_messages false
diff --git a/spec/support/pedant/run_pedant.rb b/spec/support/pedant/run_pedant.rb
deleted file mode 100644
index aac2c2d..0000000
--- a/spec/support/pedant/run_pedant.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env ruby
-require 'bundler'
-require 'bundler/setup'
-require 'chef_zero/server'
-require 'rspec/core'
-require 'chef/chef_fs/chef_fs_data_store'
-require 'chef/chef_fs/config'
-require 'tmpdir'
-require 'fileutils'
-require 'chef/version'
-require 'chef/mixin/shell_out'
-
-def start_server(chef_repo_path)
-  Dir.mkdir(chef_repo_path) if !File.exists?(chef_repo_path)
-
-  # 11.6 and below had a bug where it couldn't create the repo children automatically
-  if Chef::VERSION.to_f < 11.8
-    %w(clients cookbooks data_bags environments nodes roles users).each do |child|
-      Dir.mkdir("#{chef_repo_path}/#{child}") if !File.exists?("#{chef_repo_path}/#{child}")
-    end
-  end
-
-  # Start the new server
-  Chef::Config.repo_mode = 'everything'
-  Chef::Config.chef_repo_path = chef_repo_path
-  Chef::Config.versioned_cookbooks = true
-  chef_fs = Chef::ChefFS::Config.new.local_fs
-  data_store = Chef::ChefFS::ChefFSDataStore.new(chef_fs)
-  server = ChefZero::Server.new(:port => 8889.upto(9999), :data_store => data_store)#, :log_level => :debug)
-  server.start_background
-  server
-end
-
-tmpdir = Dir.mktmpdir
-begin
-  # Create chef repository
-  chef_repo_path = "#{tmpdir}/repo"
-
-  # Capture setup data into master_chef_repo_path
-  server = start_server(chef_repo_path)
-  so = nil
-
-  include Chef::Mixin::ShellOut
-
-  Bundler.with_clean_env do
-
-    shell_out("bundle install --gemfile spec/support/pedant/Gemfile", :live_stream => STDOUT)
-
-    pedant_cmd = "chef-pedant " +
-        " --config spec/support/pedant/pedant_config.rb" +
-        " --server '#{server.url}'" +
-        " --skip-knife --skip-validation --skip-authentication" +
-        " --skip-authorization --skip-omnibus"
-    so = shell_out("bundle exec #{pedant_cmd}", :live_stream => STDOUT, :env => {'BUNDLE_GEMFILE' => 'spec/support/pedant/Gemfile'})
-
-  end
-
-ensure
-  server.stop if server && server.running?
-  FileUtils.remove_entry_secure(tmpdir) if tmpdir
-end
-
-exit(so.exitstatus)
diff --git a/spec/support/pedant/stickywicket.pem b/spec/support/pedant/stickywicket.pem
deleted file mode 100644
index ff09e73..0000000
--- a/spec/support/pedant/stickywicket.pem
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpQIBAAKCAQEApNCkX2k+lFGDWRVhX4uClaVQrumG9XXvk6X7M2izrIg7RzMP
-Dk4thhZkpx5gr22By7PZQdMEjWC/Zo8MBjtoJ0GV0jw8npefbU1MGKs2dtpYgo0N
-Fq8fX8MdFPu4h2W3g0dMEdhT8icc2H4EjhZmdeUhUn3RIEt2duCgp3YDYnUUZx3j
-N7MHcTIdzD58ikr6zQrZzHOv+OOI86Xk9EpyEEQizOLoQxkICNrhqN7ElQDuvXaX
-BSBrYDRKH2umBMMcXzvsR/SvkmqxoEESSpIlW8zeKAWQ+znNjDC0tmTg7jZmgSP7
-siKrwo4t4ebjcmjpIoi/JKww/nGN3Uhz1ZOZuwIDAQABAoIBAQCaJQD2s0nyEeKU
-uKhfYe155Cl3zbWJcQnmv4AXbr9MiAVY6+oS6Q8ur1bn7kNjDzoruENjiuZhC7E3
-TGZklb8tp+tluyy+7vQOmBKpp8fClSfewekR5CultqhGbb8B8yIVR+NfdUHd4rLZ
-z9KWyWB+txPZQQ8L80gSmrfmpzs3IuT7oPvmtBU1Wq9QapC4n/rUohHUpUV1du4G
-0wCIF4zQTg6cbYW2YXozwVQvw+P7P3RVEqZt+aZlbVcy0fNr6jNao0hi1KFC9OH2
-VjjU+PioreoA/NU3aZPIUzmJpWtsu31yuOZxXmytAkYooCZgiEQNEHnJlNPv0RmC
-6BPMzVoBAoGBAM7yZoSNJpzdP/q1/4+H3zyy7o4I0VTW9u/GqUzhnbjm5poK30X9
-YXh/7WOVV0OoVqdO6ljRKygP3Oggf41ZEbi1C6bbsO57pksBWgx9bD9V35XscZ0J
-F1ERe//kMHwVQy74R8/cIuRwm75haLSBj5/fwGbLeeVDglJkCVqPjtuBAoGBAMvh
-qsAGG5k9u6voTcXlFwS+B5YjULhK4NSxdJ2BnOxzYzxQ3IYQZMlb2xt8yZYx/ZZK
-wjkr9rcAPEQIQZ2A6NUbGq6qCD7sSmg6UAi0CgiqTokQ/Wtag0UDvFMzwerdg/On
-37uxffpxpte8z1jYi/MxRaoTYueuc1UVnqofVIM7AoGBALZJzwPzUY/bVAADUJmd
-lYZiFsAGBF42/E05MOgH1GaK/ZWy/fkouDLsfK67XaK7JZk6ajLSDLG9R1kxRym6
-y2FoGFtiKPfo8xIenrNhx3gCrG/jVjB9UYyXWiKNXifukr9M8/SkdBfFGWsZYqGd
-fmXVMiVaFoVcce8hLxwWWEABAoGBAKcyhKX/HEj6YFqlIoqkydDAylXs1jicZ27l
-rF2yum8KXZpMMdzbutuKsdAD8Ql0K6NB4a+jByuiTMn5/11cJxUEqkgM9sArZQW+
-tH2+r+/VQpyTS0/rpXVGj/2nl2K1kI2T4R36e/aTl6CanWweAf9JK/lC9rxKyxg+
-p6SaFuObAoGACP6TKCkp2oymXlKgdUUgPrnsaz2VAw8jD5QHtx10U4wty0C8gxsk
-MLe00h09iLPyFmvJpD+MgbxV/r6RrZeVdsKdU/5LG52YgiVSTaizyy+ciEfW7xoQ
-CL5EtZd8Cn5OKinBEzzFpELqunlqepIKCIDOcLKz/cjR+3a+E6Zx5Wo=
------END RSA PRIVATE KEY-----
diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb
index da03137..492e1ea 100644
--- a/spec/support/platform_helpers.rb
+++ b/spec/support/platform_helpers.rb
@@ -26,6 +26,14 @@ def ruby_20?
   !!(RUBY_VERSION =~ /^2.0/)
 end
 
+def ruby_64bit?
+  !!(RbConfig::CONFIG['host_cpu'] =~ /x86_64/)
+end
+
+def ruby_32bit?
+  !!(RbConfig::CONFIG['host_cpu'] =~ /i686/)
+end
+
 def windows?
   !!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
 end
@@ -75,6 +83,11 @@ def windows_powershell_dsc?
   supports_dsc
 end
 
+def windows_nano_server?
+  require 'chef/platform/query_helpers'
+  Chef::Platform.windows_nano_server?
+end
+
 def mac_osx_106?
   if File.exists? "/usr/bin/sw_vers"
     result = ShellHelpers.shell_out("/usr/bin/sw_vers")
@@ -130,10 +143,18 @@ def freebsd?
   !!(RUBY_PLATFORM =~ /freebsd/)
 end
 
+def debian_family?
+  !!(ohai[:platform_family] == "debian")
+end
+
 def aix?
   !!(RUBY_PLATFORM =~ /aix/)
 end
 
+def wpar?
+  !((ohai[:virtualization] || {})[:wpar_no].nil?)
+end
+
 def supports_cloexec?
   Fcntl.const_defined?('F_SETFD') && Fcntl.const_defined?('FD_CLOEXEC')
 end
diff --git a/spec/support/shared/context/client.rb b/spec/support/shared/context/client.rb
new file mode 100644
index 0000000..eb537e9
--- /dev/null
+++ b/spec/support/shared/context/client.rb
@@ -0,0 +1,277 @@
+
+require 'spec_helper'
+
+# Stubs a basic client object
+shared_context "client" do
+  let(:fqdn)             { "hostname.example.org" }
+  let(:hostname)         { "hostname" }
+  let(:machinename)      { "machinename.example.org" }
+  let(:platform)         { "example-platform" }
+  let(:platform_version) { "example-platform-1.0" }
+
+  let(:ohai_data) do
+    {
+      :fqdn =>             fqdn,
+      :hostname =>         hostname,
+      :machinename =>      machinename,
+      :platform =>         platform,
+      :platform_version => platform_version
+    }
+  end
+
+  let(:ohai_system) do
+    ohai = instance_double("Ohai::System", :all_plugins => true, :data => ohai_data)
+    allow(ohai).to receive(:[]) do |k|
+      ohai_data[k]
+    end
+    ohai
+  end
+
+  let(:node) do
+    Chef::Node.new.tap do |n|
+      n.name(fqdn)
+      n.chef_environment("_default")
+    end
+  end
+
+  let(:json_attribs) { nil }
+  let(:client_opts) { {} }
+
+  let(:client) do
+    Chef::Config[:event_loggers] = []
+    Chef::Client.new(json_attribs, client_opts).tap do |c|
+      c.node = node
+    end
+  end
+
+  before do
+    Chef::Log.logger = Logger.new(StringIO.new)
+
+    # Node/Ohai data
+    #Chef::Config[:node_name] = fqdn
+    allow(Ohai::System).to receive(:new).and_return(ohai_system)
+  end
+end
+
+# Stubs a client for a client run.
+# Requires a client object be defined in the scope of this included context.
+# e.g.:
+#   describe "some functionality" do
+#     include_context "client"
+#     include_context "a client run"
+#     ...
+#   end
+shared_context "a client run" do
+  let(:stdout) { StringIO.new }
+  let(:stderr) { StringIO.new }
+
+  let(:api_client_exists?) { false }
+  let(:enable_fork)        { false }
+
+  let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") }
+  let(:http_node_load)     { double("Chef::REST (node)") }
+  let(:http_node_save)     { double("Chef::REST (node save)") }
+
+  let(:runner)       { instance_double("Chef::Runner") }
+  let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) }
+
+  def stub_for_register
+    # --Client.register
+    #   Make sure Client#register thinks the client key doesn't
+    #   exist, so it tries to register and create one.
+    allow(File).to receive(:exists?).and_call_original
+    expect(File).to receive(:exists?).
+      with(Chef::Config[:client_key]).
+      exactly(:once).
+      and_return(api_client_exists?)
+
+    unless api_client_exists?
+      #   Client.register will register with the validation client name.
+      expect_any_instance_of(Chef::ApiClient::Registration).to receive(:run)
+    end
+  end
+
+  def stub_for_node_load
+    #   Client.register will then turn around create another
+    #   Chef::REST object, this time with the client key it got from the
+    #   previous step.
+    expect(Chef::REST).to receive(:new).
+      with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]).
+      exactly(:once).
+      and_return(http_node_load)
+
+    # --Client#build_node
+    #   looks up the node, which we will return, then later saves it.
+    expect(Chef::Node).to receive(:find_or_create).with(fqdn).and_return(node)
+
+    # --ResourceReporter#node_load_completed
+    #   gets a run id from the server for storing resource history
+    #   (has its own tests, so stubbing it here.)
+    expect_any_instance_of(Chef::ResourceReporter).to receive(:node_load_completed)
+  end
+
+  def stub_for_sync_cookbooks
+    # --Client#setup_run_context
+    # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+    #
+    expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks)
+    expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
+    expect(http_cookbook_sync).to receive(:post).
+      with("environments/_default/cookbook_versions", {:run_list => []}).
+      and_return({})
+  end
+
+  def stub_for_converge
+    # define me
+  end
+
+  def stub_for_audit
+    # define me
+  end
+
+  def stub_for_node_save
+    # define me
+  end
+
+  def stub_for_run
+    # define me
+  end
+
+  before do
+    Chef::Config[:client_fork] = enable_fork
+    Chef::Config[:cache_path] = windows? ? 'C:\chef' : '/var/chef'
+    Chef::Config[:why_run] = false
+    Chef::Config[:audit_mode] = :enabled
+
+    stub_const("Chef::Client::STDOUT_FD", stdout)
+    stub_const("Chef::Client::STDERR_FD", stderr)
+
+    stub_for_register
+    stub_for_node_load
+    stub_for_sync_cookbooks
+    stub_for_converge
+    stub_for_audit
+    stub_for_node_save
+
+    expect_any_instance_of(Chef::RunLock).to receive(:acquire)
+    expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
+    expect_any_instance_of(Chef::RunLock).to receive(:release)
+
+    # Post conditions: check that node has been filled in correctly
+    expect(client).to receive(:run_started)
+
+    stub_for_run
+  end
+end
+
+shared_context "converge completed" do
+  def stub_for_converge
+    # --Client#converge
+    expect(Chef::Runner).to receive(:new).and_return(runner)
+    expect(runner).to receive(:converge).and_return(true)
+  end
+
+  def stub_for_node_save
+    allow(node).to receive(:data_for_save).and_return(node.for_json)
+
+    # --Client#save_updated_node
+    expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key], validate_utf8: false).and_return(http_node_save)
+    expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true)
+  end
+end
+
+shared_context "converge failed" do
+  let(:converge_error) do
+    err = Chef::Exceptions::UnsupportedAction.new("Action unsupported")
+    err.set_backtrace([ "/path/recipe.rb:15", "/path/recipe.rb:12" ])
+    err
+  end
+
+  def stub_for_converge
+    expect(Chef::Runner).to receive(:new).and_return(runner)
+    expect(runner).to receive(:converge).and_raise(converge_error)
+  end
+
+  def stub_for_node_save
+    expect(client).to_not receive(:save_updated_node)
+  end
+end
+
+shared_context "audit phase completed" do
+  def stub_for_audit
+    # -- Client#run_audits
+    expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
+    expect(audit_runner).to receive(:run).and_return(true)
+    expect(client.events).to receive(:audit_phase_complete)
+  end
+end
+
+shared_context "audit phase failed with error" do
+  let(:audit_error) do
+    err = RuntimeError.new("Unexpected audit error")
+    err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ])
+    err
+  end
+
+  def stub_for_audit
+    expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
+    expect(Chef::Audit::Logger).to receive(:read_buffer).and_return("Audit mode output!")
+    expect(audit_runner).to receive(:run).and_raise(audit_error)
+    expect(client.events).to receive(:audit_phase_failed).with(audit_error, "Audit mode output!")
+  end
+end
+
+shared_context "audit phase completed with failed controls" do
+  let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => true,
+    :num_failed => 1, :num_total => 3) }
+
+  let(:audit_error) do
+    err = Chef::Exceptions::AuditsFailed.new(audit_runner.num_failed, audit_runner.num_total)
+    err.set_backtrace([ "/path/recipe.rb:108", "/path/recipe.rb:103" ])
+    err
+  end
+
+  def stub_for_audit
+    expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
+    expect(Chef::Audit::Logger).to receive(:read_buffer).and_return("Audit mode output!")
+    expect(audit_runner).to receive(:run)
+    expect(Chef::Exceptions::AuditsFailed).to receive(:new).with(
+      audit_runner.num_failed, audit_runner.num_total
+    ).and_return(audit_error)
+    expect(client.events).to receive(:audit_phase_failed).with(audit_error, "Audit mode output!")
+  end
+end
+
+shared_context "run completed" do
+  def stub_for_run
+    expect(client).to receive(:run_completed_successfully)
+
+    # --ResourceReporter#run_completed
+    #   updates the server with the resource history
+    #   (has its own tests, so stubbing it here.)
+    expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed)
+    # --AuditReporter#run_completed
+    #   posts the audit data to server.
+    #   (has its own tests, so stubbing it here.)
+    expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed)
+  end
+end
+
+shared_context "run failed" do
+  def stub_for_run
+    expect(client).to receive(:run_failed)
+
+    # --ResourceReporter#run_completed
+    #   updates the server with the resource history
+    #   (has its own tests, so stubbing it here.)
+    expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
+    # --AuditReporter#run_completed
+    #   posts the audit data to server.
+    #   (has its own tests, so stubbing it here.)
+    expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
+  end
+
+  before do
+    expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
+  end
+end
diff --git a/spec/unit/knife/client_list_spec.rb b/spec/support/shared/context/win32.rb
similarity index 58%
copy from spec/unit/knife/client_list_spec.rb
copy to spec/support/shared/context/win32.rb
index eff01da..3dbe876 100644
--- a/spec/unit/knife/client_list_spec.rb
+++ b/spec/support/shared/context/win32.rb
@@ -1,6 +1,5 @@
 #
-# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
-# Copyright:: Copyright (c) 2011 Thomas Bishop
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,19 +15,20 @@
 # limitations under the License.
 #
 
-require 'spec_helper'
-
-describe Chef::Knife::ClientList do
-  before(:each) do
-    @knife = Chef::Knife::ClientList.new
-    @knife.name_args = [ 'adam' ]
+RSpec.shared_context "Win32" do
+  before(:all) do
+    @original_win32 = if defined?(Win32)
+      win32 = Object.send(:const_get, 'Win32')
+      Object.send(:remove_const, 'Win32')
+      win32
+    else
+      nil
+    end
+    Win32 = Module.new
   end
 
-  describe 'run' do
-    it 'should list the clients' do
-      expect(Chef::ApiClient).to receive(:list)
-      expect(@knife).to receive(:format_list_for_display)
-      @knife.run
-    end
+  after(:all) do
+    Object.send(:remove_const, 'Win32') if defined?(Win32)
+    Object.send(:const_set, 'Win32', @original_win32) if @original_win32
   end
 end
diff --git a/spec/support/shared/examples/client.rb b/spec/support/shared/examples/client.rb
new file mode 100644
index 0000000..330cb40
--- /dev/null
+++ b/spec/support/shared/examples/client.rb
@@ -0,0 +1,53 @@
+
+require 'spec_helper'
+require 'spec/support/shared/context/client'
+
+# requires platform and platform_version be defined
+shared_examples "a completed run" do
+  include_context "run completed"
+
+  it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do
+    # This is what we're testing.
+    expect(client.run).to be true
+
+    # fork is stubbed, so we can see the outcome of the run
+    expect(node.automatic_attrs[:platform]).to eq(platform)
+    expect(node.automatic_attrs[:platform_version]).to eq(platform_version)
+  end
+end
+
+shared_examples "a completed run with audit failure" do
+  include_context "run completed"
+
+  before do
+    expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
+  end
+
+  it "converges, runs audits, saves the node and raises the error in a wrapping error" do
+    expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+      expect(error.wrapped_errors.size).to eq(run_errors.size)
+      run_errors.each do |run_error|
+        expect(error.wrapped_errors).to include(run_error)
+        expect(error.backtrace).to include(*run_error.backtrace)
+      end
+    end
+
+    # fork is stubbed, so we can see the outcome of the run
+    expect(node.automatic_attrs[:platform]).to eq(platform)
+    expect(node.automatic_attrs[:platform_version]).to eq(platform_version)
+  end
+end
+
+shared_examples "a failed run" do
+  include_context "run failed"
+
+  it "skips node save and raises the error in a wrapping error" do
+    expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+      expect(error.wrapped_errors.size).to eq(run_errors.size)
+      run_errors.each do |run_error|
+        expect(error.wrapped_errors).to include(run_error)
+        expect(error.backtrace).to include(*run_error.backtrace)
+      end
+    end
+  end
+end
diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb
index 4f8e2f5..3ce3c9c 100644
--- a/spec/support/shared/functional/file_resource.rb
+++ b/spec/support/shared/functional/file_resource.rb
@@ -592,10 +592,6 @@ shared_examples_for "a configured file resource" do
             File.open(path, "wb") { |f| f.write(wrong_content) }
           end
 
-          it "updates the source file content" do
-            skip
-          end
-
           it "marks the resource as updated" do
             resource.run_action(:create)
             expect(resource).to be_updated_by_last_action
diff --git a/spec/support/shared/functional/securable_resource.rb b/spec/support/shared/functional/securable_resource.rb
index e016bb6..b3c3235 100644
--- a/spec/support/shared/functional/securable_resource.rb
+++ b/spec/support/shared/functional/securable_resource.rb
@@ -163,9 +163,6 @@ shared_examples_for "a securable resource with existing target" do
     let(:desired_gid) { 1337 }
     let(:expected_gid) { 1337 }
 
-    skip "should set an owner (Rerun specs under root)", :requires_unprivileged_user => true
-    skip "should set a group (Rerun specs under root)",  :requires_unprivileged_user => true
-
     describe "when setting the owner", :requires_root do
       before do
         resource.owner expected_user_name
@@ -205,11 +202,6 @@ shared_examples_for "a securable resource with existing target" do
         resource.run_action(:create)
       end
 
-      it "should set permissions as specified" do
-        pending("Linux does not support lchmod")
-        expect{ File.lstat(path).mode & 007777 }.to eq(@mode_string.oct & 007777)
-      end
-
       it "is marked as updated only if changes are made" do
         expect(resource.updated_by_last_action?).to eq(expect_updated?)
       end
@@ -222,15 +214,28 @@ shared_examples_for "a securable resource with existing target" do
         resource.run_action(:create)
       end
 
-      it "should set permissions in numeric form as a ruby-interpreted octal" do
-        pending('Linux does not support lchmod')
-        expect{ File.lstat(path).mode & 007777 }.to eq(@mode_integer & 007777)
-      end
-
       it "is marked as updated only if changes are made" do
         expect(resource.updated_by_last_action?).to eq(expect_updated?)
       end
     end
+
+    describe "when setting the suid bit", :requires_root do
+      before do
+        @suid_mode = 04776
+        resource.mode @suid_mode
+        resource.run_action(:create)
+      end
+
+      it "should set the suid bit" do
+        expect(File.lstat(path).mode & 007777).to eq(@suid_mode & 007777)
+      end
+
+      it "should retain the suid bit when updating the user" do
+        resource.user 1338
+        resource.run_action(:create)
+        expect(File.lstat(path).mode & 007777).to eq(@suid_mode & 007777)
+      end
+    end
   end
 
   context "on Windows", :windows_only do
@@ -288,17 +293,13 @@ shared_examples_for "a securable resource without existing target" do
 
   include_context "diff disabled"
 
-  context "on Unix", :unix_only do
-    skip "if we need any securable resource tests on Unix without existing target resource."
-  end
-
   context "on Windows", :windows_only do
     include_context "use Windows permissions"
 
-    it "sets owner to Administrators on create if owner is not specified" do
+    it "leaves owner as system default on create if owner is not specified" do
       expect(File.exist?(path)).to eq(false)
       resource.run_action(:create)
-      expect(descriptor.owner).to eq(SID.Administrators)
+      expect(descriptor.owner).to eq(SID.default_security_object_owner)
     end
 
     it "sets owner when owner is specified" do
@@ -318,22 +319,24 @@ shared_examples_for "a securable resource without existing target" do
     end
 
     it "leaves owner alone if owner is not specified and resource already exists" do
-      # Set owner to Guest so it's not the same as the current user (which is the default on create)
-      resource.owner 'Guest'
+      arbitrary_non_default_owner = SID.Guest
+      expect(arbitrary_non_default_owner).not_to eq(SID.default_security_object_owner)
+
+      resource.owner 'Guest' # Change to arbitrary_non_default_owner once issue #1508 is fixed
       resource.run_action(:create)
-      expect(descriptor.owner).to eq(SID.Guest)
+      expect(descriptor.owner).to eq(arbitrary_non_default_owner)
 
       new_resource = create_resource
       expect(new_resource.owner).to eq(nil)
       new_resource.run_action(:create)
-      expect(descriptor.owner).to eq(SID.Guest)
+      expect(descriptor.owner).to eq(arbitrary_non_default_owner)
     end
 
-    it "sets group to None on create if group is not specified" do
+    it "leaves group as system default on create if group is not specified" do
       expect(resource.group).to eq(nil)
       expect(File.exist?(path)).to eq(false)
       resource.run_action(:create)
-      expect(descriptor.group).to eq(SID.None)
+      expect(descriptor.group).to eq(SID.default_security_object_group)
     end
 
     it "sets group when group is specified" do
@@ -346,23 +349,18 @@ shared_examples_for "a securable resource without existing target" do
       expect { resource.group 'Lance "The Nose" Glindenberry III' }.to raise_error(Chef::Exceptions::ValidationFailed)
     end
 
-    it "sets group when group is specified with a \\" do
-      pending("Need to find a group containing a backslash that is on most peoples' machines")
-      resource.group "#{ENV['COMPUTERNAME']}\\Administrators"
-      resource.run_action(:create)
-      expect{ descriptor.group }.to eq(SID.Everyone)
-    end
-
     it "leaves group alone if group is not specified and resource already exists" do
-      # Set group to Everyone so it's not the default (None)
-      resource.group 'Everyone'
+      arbitrary_non_default_group = SID.Everyone
+      expect(arbitrary_non_default_group).not_to eq(SID.default_security_object_group)
+
+      resource.group 'Everyone' # Change to arbitrary_non_default_group once issue #1508 is fixed
       resource.run_action(:create)
-      expect(descriptor.group).to eq(SID.Everyone)
+      expect(descriptor.group).to eq(arbitrary_non_default_group)
 
       new_resource = create_resource
       expect(new_resource.group).to eq(nil)
       new_resource.run_action(:create)
-      expect(descriptor.group).to eq(SID.Everyone)
+      expect(descriptor.group).to eq(arbitrary_non_default_group)
     end
 
     describe "with rights and deny_rights attributes" do
diff --git a/spec/support/shared/functional/securable_resource_with_reporting.rb b/spec/support/shared/functional/securable_resource_with_reporting.rb
index 37fc538..3176ebb 100644
--- a/spec/support/shared/functional/securable_resource_with_reporting.rb
+++ b/spec/support/shared/functional/securable_resource_with_reporting.rb
@@ -279,14 +279,14 @@ shared_examples_for "a securable resource with reporting" do
       end
 
       it "has empty values for file metadata in 'current_resource'" do
-        pending "windows reporting not yet fully supported"
+        skip "windows reporting not yet fully supported"
         expect(current_resource.owner).to be_nil
         expect(current_resource.expanded_rights).to be_nil
       end
 
       context "and no security metadata is specified in new_resource" do
         before do
-          pending "windows reporting not yet fully supported"
+          skip "windows reporting not yet fully supported"
         end
 
         it "sets the metadata values on the new_resource as strings after creating" do
@@ -322,7 +322,7 @@ shared_examples_for "a securable resource with reporting" do
         let(:expected_user_name) { 'domain\user' }
 
         before do
-          pending "windows reporting not yet fully supported"
+          skip "windows reporting not yet fully supported"
           resource.owner(expected_user_name)
           resource.run_action(:create)
         end
@@ -336,7 +336,7 @@ shared_examples_for "a securable resource with reporting" do
 
     context "when the target file exists" do
       before do
-        pending "windows reporting not yet fully supported"
+        skip "windows reporting not yet fully supported"
         FileUtils.touch(resource.path)
         resource.action(:create)
       end
diff --git a/spec/support/shared/functional/win32_service.rb b/spec/support/shared/functional/win32_service.rb
index 7dd1920..2ee1a8a 100644
--- a/spec/support/shared/functional/win32_service.rb
+++ b/spec/support/shared/functional/win32_service.rb
@@ -46,7 +46,8 @@ shared_context "using Win32::Service" do
       :service_name => "spec-service",
       :service_display_name => "Spec Test Service",
       :service_description => "Service for testing Chef::Application::WindowsServiceManager.",
-      :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../platforms/win32/spec_service.rb'))
+      :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../platforms/win32/spec_service.rb')),
+      :delayed_start => true
     }
   }
 
diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb
index 35b86dc..d84c06c 100644
--- a/spec/support/shared/functional/windows_script.rb
+++ b/spec/support/shared/functional/windows_script.rb
@@ -19,14 +19,15 @@
 # Shared context used by both Powershell and Batch script provider
 # tests.
 
+require 'chef/platform/query_helpers'
+
 shared_context Chef::Resource::WindowsScript do
   before(:all) do
-
-    ohai_reader = Ohai::System.new
-    ohai_reader.all_plugins("platform")
+    @ohai_reader = Ohai::System.new
+    @ohai_reader.all_plugins(["platform", "kernel"])
 
     new_node = Chef::Node.new
-    new_node.consume_external_attrs(ohai_reader.data,{})
+    new_node.consume_external_attrs(@ohai_reader.data,{})
 
     events = Chef::EventDispatch::Dispatcher.new
 
@@ -51,12 +52,11 @@ shared_context Chef::Resource::WindowsScript do
 
   shared_examples_for "a script resource with architecture attribute" do
     context "with the given architecture attribute value" do
-      let(:resource_architecture) { architecture }
       let(:expected_architecture) do
-        if architecture
-          expected_architecture = architecture
+        if resource_architecture
+          expected_architecture = resource_architecture
         else
-          expected_architecture = :i386
+          expected_architecture = @ohai_reader.data['kernel']['machine'].to_sym
         end
       end
       let(:expected_architecture_output) do
@@ -77,16 +77,16 @@ shared_context Chef::Resource::WindowsScript do
 
       before(:each) do
         resource.code resource_command
-        (resource.architecture architecture) if architecture
+        (resource.architecture resource_architecture) if resource_architecture
         resource.returns(0)
       end
 
-      it "should create a process with the expected architecture" do
+      it "creates a process with the expected architecture" do
         resource.run_action(:run)
         expect(get_process_architecture).to eq(expected_architecture_output.downcase)
       end
 
-      it "should execute guards with the same architecture as the resource" do
+      it "executes guards with the same architecture as the resource" do
         resource.only_if resource_guard_command
         resource.run_action(:run)
         expect(get_process_architecture).to eq(expected_architecture_output.downcase)
@@ -94,18 +94,32 @@ shared_context Chef::Resource::WindowsScript do
         expect(get_guard_process_architecture).to eq(get_process_architecture)
       end
 
-      let (:architecture) { :x86_64 }
-      it "should execute a 64-bit guard if the guard's architecture is specified as 64-bit", :windows64_only do
-        resource.only_if resource_guard_command, :architecture => :x86_64
-        resource.run_action(:run)
-        expect(get_guard_process_architecture).to eq('amd64')
+      context "when the guard's architecture is specified as 64-bit" do
+        let (:guard_architecture) { :x86_64 }
+        it "executes a 64-bit guard", :windows64_only do
+          resource.only_if resource_guard_command, :architecture => guard_architecture
+          resource.run_action(:run)
+          expect(get_guard_process_architecture).to eq('amd64')
+        end
       end
 
-      let (:architecture) { :i386 }
-      it "should execute a 32-bit guard if the guard's architecture is specified as 32-bit" do
-        resource.only_if resource_guard_command, :architecture => :i386
-        resource.run_action(:run)
-        expect(get_guard_process_architecture).to eq('x86')
+      context "when the guard's architecture is specified as 32-bit", :not_supported_on_nano do
+        let (:guard_architecture) { :i386 }
+        it "executes a 32-bit guard" do
+          resource.only_if resource_guard_command, :architecture => guard_architecture
+          resource.run_action(:run)
+          expect(get_guard_process_architecture).to eq('x86')
+        end
+      end
+
+      context "when the guard's architecture is specified as 32-bit", :windows_nano_only do
+        let (:guard_architecture) { :i386 }
+        it "raises an error" do
+          resource.only_if resource_guard_command, :architecture => guard_architecture
+          expect{ resource.run_action(:run) }.to raise_error(
+            Chef::Exceptions::Win32ArchitectureIncorrect,
+            /cannot execute script with requested architecture 'i386' on Windows Nano Server/)
+        end
       end
     end
   end
@@ -114,7 +128,28 @@ shared_context Chef::Resource::WindowsScript do
 
     describe "when the run action is invoked on Windows" do
       it "executes the script code" do
-        resource.code("@whoami > #{script_output_path}")
+        resource.code("whoami > \"#{script_output_path}\"")
+        resource.returns(0)
+        resource.run_action(:run)
+      end
+    end
+
+    context "when $env:TMP has a space" do
+      before(:each) do
+        @dir = Dir.mktmpdir("Jerry Smith")
+        @original_env = ENV.to_hash.dup
+        ENV.delete('TMP')
+        ENV['TMP'] = @dir
+      end
+
+      after(:each) do
+        FileUtils.remove_entry_secure(@dir)
+        ENV.clear
+        ENV.update(@original_env)
+      end
+
+      it "executes the script code" do
+        resource.code("whoami > \"#{script_output_path}\"")
         resource.returns(0)
         resource.run_action(:run)
       end
@@ -122,6 +157,8 @@ shared_context Chef::Resource::WindowsScript do
 
     context "when evaluating guards" do
       it "has a guard_interpreter attribute set to the short name of the resource" do
+        pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server?
+
         expect(resource.guard_interpreter).to eq(resource.resource_name)
         resource.not_if "findstr.exe /thiscommandhasnonzeroexitstatus"
         expect(Chef::Resource).to receive(:resource_for_node).and_call_original
@@ -131,17 +168,17 @@ shared_context Chef::Resource::WindowsScript do
     end
 
     context "when the architecture attribute is not set" do
-      let(:architecture) { nil }
+      let(:resource_architecture) { nil }
       it_behaves_like "a script resource with architecture attribute"
     end
 
-    context "when the architecture attribute is :i386" do
-      let(:architecture) { :i386 }
+    context "when the architecture attribute is :i386", :not_supported_on_nano do
+      let(:resource_architecture) { :i386 }
       it_behaves_like "a script resource with architecture attribute"
     end
 
     context "when the architecture attribute is :x86_64" do
-      let(:architecture) { :x86_64 }
+      let(:resource_architecture) { :x86_64 }
       it_behaves_like "a script resource with architecture attribute"
     end
   end
diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb
index e6942c6..7d62a69 100644
--- a/spec/support/shared/integration/integration_helper.rb
+++ b/spec/support/shared/integration/integration_helper.rb
@@ -22,14 +22,25 @@ require 'fileutils'
 require 'chef/config'
 require 'chef/json_compat'
 require 'chef/server_api'
-require 'chef_zero/rspec'
 require 'support/shared/integration/knife_support'
 require 'support/shared/integration/app_server_support'
+require 'cheffish/rspec/chef_run_support'
 require 'spec_helper'
 
+module Cheffish
+  class BasicChefClient
+    def_delegators :@run_context, :before_notifications
+  end
+end
+
 module IntegrationSupport
   include ChefZero::RSpec
 
+  def self.included(includer_class)
+    includer_class.extend(Cheffish::RSpec::ChefRunSupport)
+    includer_class.extend(ClassMethods)
+  end
+
   module ClassMethods
     include ChefZero::RSpec
 
@@ -49,10 +60,6 @@ module IntegrationSupport
     end
   end
 
-  def self.included(includer_class)
-    includer_class.extend(ClassMethods)
-  end
-
   def api
     Chef::ServerAPI.new
   end
diff --git a/spec/support/shared/shared_examples.rb b/spec/support/shared/shared_examples.rb
index b20c65f..550fa2e 100644
--- a/spec/support/shared/shared_examples.rb
+++ b/spec/support/shared/shared_examples.rb
@@ -1,7 +1,7 @@
 # For storing any examples shared between multiple tests
 
 # Any object which defines a .to_json should import this test
-shared_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+shared_examples "to_json equivalent to Chef::JSONCompat.to_json" do
 
   let(:jsonable) {
     raise "You must define the subject when including this test"
diff --git a/spec/support/shared/unit/api_versioning.rb b/spec/support/shared/unit/api_versioning.rb
new file mode 100644
index 0000000..05a4117
--- /dev/null
+++ b/spec/support/shared/unit/api_versioning.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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/exceptions"
+
+shared_examples_for "version handling" do
+  let(:response_406) { OpenStruct.new(:code => '406') }
+  let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+
+  before do
+    allow(rest_v1).to receive(http_verb).and_raise(exception_406)
+  end
+
+  context "when the server does not support the min or max server API version that Chef::UserV1 supports" do
+    before do
+      allow(object).to receive(:server_client_api_version_intersection).and_return([])
+    end
+
+    it "raises the original exception" do
+      expect{ object.send(method) }.to raise_error(exception_406)
+    end
+  end # when the server does not support the min or max server API version that Chef::UserV1 supports
+end # version handling
+
+shared_examples_for "user and client reregister" do
+  let(:response_406) { OpenStruct.new(:code => '406') }
+  let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+  let(:generic_exception) { Exception.new }
+  let(:min_version) { "2" }
+  let(:max_version) { "5" }
+  let(:return_hash_406) {
+    {
+      "min_version" => min_version,
+      "max_version" => max_version,
+      "request_version" => "30"
+    }
+  }
+
+  context "when V0 is not supported by the server" do
+    context "when the exception is 406 and returns x-ops-server-api-version header" do
+      before do
+        allow(rest_v0).to receive(:put).and_raise(exception_406)
+        allow(response_406).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash_406))
+      end
+
+      it "raises an error about only V0 being supported" do
+        expect(object).to receive(:reregister_only_v0_supported_error_msg).with(max_version, min_version)
+        expect{ object.reregister }.to raise_error(Chef::Exceptions::OnlyApiVersion0SupportedForAction)
+      end
+
+    end
+    context "when the exception is not versioning related" do
+      before do
+        allow(rest_v0).to receive(:put).and_raise(generic_exception)
+      end
+
+      it "raises the original error" do
+        expect{ object.reregister }.to raise_error(generic_exception)
+      end
+    end
+  end
+end
diff --git a/spec/support/shared/unit/execute_resource.rb b/spec/support/shared/unit/execute_resource.rb
index e969a2e..3a88ff8 100644
--- a/spec/support/shared/unit/execute_resource.rb
+++ b/spec/support/shared/unit/execute_resource.rb
@@ -111,6 +111,11 @@ shared_examples_for "an execute resource" do
     expect(@resource.creates).to eql("something")
   end
 
+  it "should accept a boolean for live streaming" do
+    @resource.live_stream true
+    expect(@resource.live_stream).to be true
+  end
+
   describe "when it has cwd, environment, group, path, return value, and a user" do
     before do
       @resource.command("grep")
diff --git a/lib/chef/util/powershell/ps_credential.rb b/spec/support/shared/unit/knife_shared.rb
similarity index 52%
copy from lib/chef/util/powershell/ps_credential.rb
copy to spec/support/shared/unit/knife_shared.rb
index 01f8c27..8c9010f 100644
--- a/lib/chef/util/powershell/ps_credential.rb
+++ b/spec/support/shared/unit/knife_shared.rb
@@ -1,7 +1,7 @@
 #
-# Author:: Jay Mundrawala (<jdm at chef.io>)
-#
+# Author:: Tyler Cloke (<tyler at chef.io>)
 # Copyright:: Copyright (c) 2015 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.
@@ -16,23 +16,25 @@
 # limitations under the License.
 #
 
-require 'chef/win32/crypto' if Chef::Platform.windows?
 
-class Chef::Util::Powershell
-  class PSCredential
-    def initialize(username, password)
-      @username = username
-      @password = password
+shared_examples_for "mandatory field missing" do
+  context "when field is nil" do
+    before do
+      knife.name_args = name_args
     end
 
-    def to_psobject
-      "New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
+    it "exits 1" do
+      expect { knife.run }.to raise_error(SystemExit)
     end
 
-    private
+    it "prints the usage" do
+      expect(knife).to receive(:show_usage)
+      expect { knife.run }.to raise_error(SystemExit)
+    end
 
-    def encrypt(str)
-      Chef::ReservedNames::Win32::Crypto.encrypt(str)
+    it "prints a relevant error message" do
+      expect { knife.run }.to raise_error(SystemExit)
+      expect(stderr.string).to match /You must specify a #{fieldname}/
     end
   end
 end
diff --git a/spec/support/shared/unit/mock_shellout.rb b/spec/support/shared/unit/mock_shellout.rb
new file mode 100644
index 0000000..7c3e49e
--- /dev/null
+++ b/spec/support/shared/unit/mock_shellout.rb
@@ -0,0 +1,46 @@
+#
+# Author:: John Keiser <jkeiser at chef.io>
+# Copyright:: Copyright (c) 2015 John Keiser.
+# 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.
+#
+
+#
+# Mocks shellout results. Examples:
+#   mock_shellout_command("systemctl --all", exitstatus: 1)
+#
+class MockShellout
+  module RSpec
+    def mock_shellout_command(command, **result)
+      allow(::Mixlib::ShellOut).to receive(:new).with(command, anything).and_return MockShellout.new(result)
+    end
+  end
+
+  def initialize(**properties)
+    @properties = {
+      stdout: "",
+      stderr: "",
+      exitstatus: 0
+    }.merge(properties)
+  end
+  def method_missing(name, *args)
+    @properties[name.to_sym]
+  end
+  def error?
+    exitstatus != 0
+  end
+  def error!
+    raise Mixlib::ShellOut::ShellCommandFailed, "Expected process to exit with 0, but received #{exitstatus}" if error?
+  end
+end
diff --git a/spec/support/shared/unit/platform_introspector.rb b/spec/support/shared/unit/platform_introspector.rb
index 9f42c98..df24370 100644
--- a/spec/support/shared/unit/platform_introspector.rb
+++ b/spec/support/shared/unit/platform_introspector.rb
@@ -32,6 +32,7 @@ shared_examples_for "a platform introspector" do
     # The following @platform_hash keys are used for testing version constraints
     @platform_hash['exact_match'] = { '1.2.3' => 'exact', '>= 1.0' => 'not exact'}
     @platform_hash['multiple_matches'] = { '~> 2.3.4' => 'matched ~> 2.3.4', '>= 2.3' => 'matched >=2.3' }
+    @platform_hash['invalid_cookbook_version'] = {'>= 21' => 'Matches a single number'}
     @platform_hash['successful_matches'] = { '< 3.0' => 'matched < 3.0', '>= 3.0' => 'matched >= 3.0' }
 
     @platform_family_hash = {
@@ -95,6 +96,12 @@ shared_examples_for "a platform introspector" do
     expect {platform_introspector.value_for_platform(@platform_hash)}.to raise_error(RuntimeError)
   end
 
+  it 'should not require .0 to match >= 21.0' do
+    node.automatic_attrs[:platform] = 'invalid_cookbook_version'
+    node.automatic_attrs[:platform_version] = '21'
+    expect(platform_introspector.value_for_platform(@platform_hash)).to eq('Matches a single number')
+  end
+
   it 'should return the value for that match' do
     node.automatic_attrs[:platform] = 'successful_matches'
     node.automatic_attrs[:platform_version] = '2.9'
diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb
index 86f32c9..ff9e271 100644
--- a/spec/support/shared/unit/provider/file.rb
+++ b/spec/support/shared/unit/provider/file.rb
@@ -255,7 +255,7 @@ shared_examples_for Chef::Provider::File do
     context "examining file security metadata on Unix with a file that exists" do
       before do
         # fake that we're on unix even if we're on windows
-        allow(Chef::Platform).to receive(:windows?).and_return(false)
+        allow(ChefConfig).to receive(:windows?).and_return(false)
         # mock up the filesystem to behave like unix
         setup_normal_file
         stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
@@ -331,7 +331,7 @@ shared_examples_for Chef::Provider::File do
     context "examining file security metadata on Unix with a file that does not exist" do
       before do
         # fake that we're on unix even if we're on windows
-        allow(Chef::Platform).to receive(:windows?).and_return(false)
+        allow(ChefConfig).to receive(:windows?).and_return(false)
         setup_missing_file
       end
 
@@ -380,7 +380,7 @@ shared_examples_for Chef::Provider::File do
 
     before do
       # fake that we're on unix even if we're on windows
-      allow(Chef::Platform).to receive(:windows?).and_return(false)
+      allow(ChefConfig).to receive(:windows?).and_return(false)
       # mock up the filesystem to behave like unix
       setup_normal_file
       stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
@@ -465,11 +465,13 @@ shared_examples_for Chef::Provider::File do
         t
       }
 
-      let(:verification) { double("Verification") }
+      let(:verification) { instance_double(Chef::Resource::File::Verification) }
+      let(:verification_fail) { instance_double(Chef::Resource::File::Verification) }
 
       context "with user-supplied verifications" do
         it "calls #verify on each verification with tempfile path" do
-          allow(Chef::Resource::File::Verification).to receive(:new).and_return(verification)
+          allow(Chef::Resource::File::Verification).to(
+            receive(:new).with(anything(), "true", anything()).and_return(verification))
           provider.new_resource.verify "true"
           provider.new_resource.verify "true"
           expect(verification).to receive(:verify).with(tempfile.path).twice.and_return(true)
@@ -477,10 +479,14 @@ shared_examples_for Chef::Provider::File do
         end
 
         it "raises an exception if any verification fails" do
+          allow(Chef::Resource::File::Verification).to(
+            receive(:new).with(anything(), "true", anything()).and_return(verification))
+          allow(Chef::Resource::File::Verification).to(
+            receive(:new).with(anything(), "false", anything()).and_return(verification_fail))
           provider.new_resource.verify "true"
           provider.new_resource.verify "false"
-          allow(verification).to receive(:verify).with("true").and_return(true)
-          allow(verification).to receive(:verify).with("false").and_return(false)
+          expect(verification).to receive(:verify).with(tempfile.path).and_return(true)
+          expect(verification_fail).to receive(:verify).with(tempfile.path).and_return(false)
           expect{provider.send(:do_validate_content)}.to raise_error(Chef::Exceptions::ValidationFailed)
         end
       end
@@ -529,26 +535,49 @@ shared_examples_for Chef::Provider::File do
                                   :for_reporting => diff_for_reporting )
             allow(diff).to receive(:diff).with(resource_path, tempfile_path).and_return(true)
             expect(provider).to receive(:diff).at_least(:once).and_return(diff)
-            expect(provider).to receive(:managing_content?).at_least(:once).and_return(true)
             expect(provider).to receive(:checksum).with(tempfile_path).and_return(tempfile_sha256)
-            expect(provider).to receive(:checksum).with(resource_path).and_return(tempfile_sha256)
+            allow(provider).to receive(:managing_content?).and_return(true)
+            allow(provider).to receive(:checksum).with(resource_path).and_return(tempfile_sha256)
+            expect(resource).not_to receive(:checksum).with(tempfile_sha256)  # do not mutate the new resource
             expect(provider.deployment_strategy).to receive(:deploy).with(tempfile_path, normalized_path)
           end
           context "when the file was created" do
             before { expect(provider).to receive(:needs_creating?).at_least(:once).and_return(true) }
-            it "does not backup the file and does not produce a diff for reporting" do
+            it "does not backup the file" do
               expect(provider).not_to receive(:do_backup)
               provider.send(:do_contents_changes)
+            end
+
+            it "does not produce a diff for reporting" do
+              provider.send(:do_contents_changes)
               expect(resource.diff).to be_nil
             end
+
+            it "renders the final checksum correctly for reporting" do
+              provider.send(:do_contents_changes)
+              expect(resource.state_for_resource_reporter[:checksum]).to eql(tempfile_sha256)
+            end
           end
           context "when the file was not created" do
-            before { expect(provider).to receive(:needs_creating?).at_least(:once).and_return(false) }
-            it "backs up the file and produces a diff for reporting" do
+            before do
+              allow(provider).to receive(:do_backup)  # stub do_backup
+              expect(provider).to receive(:needs_creating?).at_least(:once).and_return(false)
+            end
+
+            it "backs up the file" do
               expect(provider).to receive(:do_backup)
               provider.send(:do_contents_changes)
+            end
+
+            it "produces a diff for reporting" do
+              provider.send(:do_contents_changes)
               expect(resource.diff).to eq(diff_for_reporting)
             end
+
+            it "renders the final checksum correctly for reporting" do
+              provider.send(:do_contents_changes)
+              expect(resource.state_for_resource_reporter[:checksum]).to eql(tempfile_sha256)
+            end
           end
         end
 
diff --git a/spec/support/shared/unit/user_and_client_shared.rb b/spec/support/shared/unit/user_and_client_shared.rb
new file mode 100644
index 0000000..bc5ffa0
--- /dev/null
+++ b/spec/support/shared/unit/user_and_client_shared.rb
@@ -0,0 +1,115 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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.
+#
+
+shared_examples_for "user or client create" do
+
+  context "when server API V1 is valid on the Chef Server receiving the request" do
+
+    it "creates a new object via the API" do
+      expect(rest_v1).to receive(:post).with(url, payload).and_return({})
+      object.create
+    end
+
+    it "creates a new object via the API with a public_key when it exists" do
+      object.public_key "some_public_key"
+      expect(rest_v1).to receive(:post).with(url, payload.merge({:public_key => "some_public_key"})).and_return({})
+      object.create
+    end
+
+    context "raise error when create_key and public_key are both set" do
+
+      before do
+        object.public_key "key"
+        object.create_key true
+      end
+
+      it "rasies the proper error" do
+        expect { object.create }.to raise_error(error)
+      end
+    end
+
+    context "when create_key == true" do
+      before do
+        object.create_key true
+      end
+
+      it "creates a new object via the API with create_key" do
+        expect(rest_v1).to receive(:post).with(url, payload.merge({:create_key => true})).and_return({})
+        object.create
+      end
+    end
+
+    context "when chef_key is returned by the server" do
+      let(:chef_key) {
+        {
+          "chef_key" => {
+            "public_key" => "some_public_key"
+          }
+        }
+      }
+
+      it "puts the public key into the objectr returned by create" do
+        expect(rest_v1).to receive(:post).with(url, payload).and_return(payload.merge(chef_key))
+        new_object = object.create
+        expect(new_object.public_key).to eq("some_public_key")
+      end
+
+      context "when private_key is returned in chef_key" do
+        let(:chef_key) {
+          {
+            "chef_key" => {
+              "public_key" => "some_public_key",
+              "private_key" => "some_private_key"
+            }
+          }
+        }
+
+        it "puts the private key into the object returned by create" do
+          expect(rest_v1).to receive(:post).with(url, payload).and_return(payload.merge(chef_key))
+          new_object = object.create
+          expect(new_object.private_key).to eq("some_private_key")
+        end
+      end
+    end # when chef_key is returned by the server
+
+  end # when server API V1 is valid on the Chef Server receiving the request
+
+  context "when server API V1 is not valid on the Chef Server receiving the request" do
+
+    context "when the server supports API V0" do
+      before do
+        allow(object).to receive(:server_client_api_version_intersection).and_return([0])
+        allow(rest_v1).to receive(:post).and_raise(exception_406)
+      end
+
+      it "creates a new object via the API" do
+        expect(rest_v0).to receive(:post).with(url, payload).and_return({})
+        object.create
+      end
+
+      it "creates a new object via the API with a public_key when it exists" do
+        object.public_key "some_public_key"
+        expect(rest_v0).to receive(:post).with(url, payload.merge({:public_key => "some_public_key"})).and_return({})
+        object.create
+      end
+
+    end # when the server supports API V0
+  end # when server API V1 is not valid on the Chef Server receiving the request
+
+end # user or client create
+
diff --git a/spec/tiny_server.rb b/spec/tiny_server.rb
index a2cfe16..ec2a11f 100644
--- a/spec/tiny_server.rb
+++ b/spec/tiny_server.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -66,9 +66,11 @@ module TinyServer
         @server = Server.setup(@options) do
           run API.instance
         end
+        @old_handler = trap(:INT, "EXIT")
         @server.start
       end
       block_until_started
+      trap(:INT, @old_handler)
     end
 
     def url
@@ -92,9 +94,11 @@ module TinyServer
       true
     rescue Errno::ECONNREFUSED, EOFError, Errno::ECONNRESET => e
       sleep 0.1
+      true
       # If the host has ":::1 localhost" in its hosts file and if IPv6
       # is not enabled we can get NetworkUnreachable exception...
-    rescue Errno::ENETUNREACH => e
+    rescue Errno::ENETUNREACH, Net::ReadTimeout, IO::EAGAINWaitReadable,
+        Errno::EHOSTUNREACH => e
       sleep 0.1
       false
     end
diff --git a/spec/unit/api_client/registration_spec.rb b/spec/unit/api_client/registration_spec.rb
index d6230af..58d6499 100644
--- a/spec/unit/api_client/registration_spec.rb
+++ b/spec/unit/api_client/registration_spec.rb
@@ -35,7 +35,7 @@ describe Chef::ApiClient::Registration do
     File.open(Chef::Config[:validation_key], "r") {|f| f.read.chomp }
   end
 
-  let(:http_mock) { double("Chef::REST mock") }
+  let(:http_mock) { double("Chef::ServerAPI mock") }
 
   let(:expected_post_data) do
     { :name => client_name, :admin => false, :public_key => generated_public_key.to_pem }
@@ -93,9 +93,10 @@ describe Chef::ApiClient::Registration do
   end
 
   it "has an HTTP client configured with validator credentials" do
-    expect(registration.http_api).to be_a_kind_of(Chef::REST)
-    expect(registration.http_api.client_name).to eq("test-validator")
-    expect(registration.http_api.signing_key).to eq(private_key_data)
+    expect(registration.http_api).to be_a_kind_of(Chef::ServerAPI)
+    expect(registration.http_api.options[:client_name]).to eq("test-validator")
+    auth = registration.http_api.middlewares.select { |klass| klass.kind_of? Chef::HTTP::Authenticator }.first
+    expect(auth.client_name).to eq("test-validator")
   end
 
   describe "when creating/updating the client on the server" do
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb
index 7668e31..a0e399b 100644
--- a/spec/unit/api_client_spec.rb
+++ b/spec/unit/api_client_spec.rb
@@ -21,6 +21,11 @@ require 'spec_helper'
 require 'chef/api_client'
 require 'tempfile'
 
+# DEPRECATION NOTE
+#
+# This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1,
+# which will be moved to this namespace. New development should occur in
+# Chef::ApiClientV1 until the time before Chef 13.
 describe Chef::ApiClient do
   before(:each) do
     @client = Chef::ApiClient.new
@@ -123,10 +128,6 @@ describe Chef::ApiClient do
     it "does not include the private key if not present" do
       expect(@json).not_to include("private_key")
     end
-
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
-      let(:jsonable) { @client }
-    end
   end
 
   describe "when deserializing from JSON (string) using ApiClient#from_json" do
@@ -222,8 +223,8 @@ describe Chef::ApiClient do
       "validator" => true,
       "json_class" => "Chef::ApiClient"
       }
-      @http_client = double("Chef::REST mock")
-      allow(Chef::REST).to receive(:new).and_return(@http_client)
+      @http_client = double("Chef::ServerAPI mock")
+      allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
       expect(@http_client).to receive(:get).with("clients/black").and_return(client)
       @client = Chef::ApiClient.load(client['name'])
     end
@@ -269,18 +270,13 @@ describe Chef::ApiClient do
       File.open(Chef::Config[:client_key], "r") {|f| f.read.chomp }
     end
 
-    it "has an HTTP client configured with default credentials" do
-      expect(@client.http_api).to be_a_kind_of(Chef::REST)
-      expect(@client.http_api.client_name).to eq("silent-bob")
-      expect(@client.http_api.signing_key.to_s).to eq(private_key_data)
-    end
   end
 
 
   describe "when requesting a new key" do
     before do
       @http_client = double("Chef::REST mock")
-      allow(Chef::REST).to receive(:new).and_return(@http_client)
+      allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
     end
 
     context "and the client does not exist on the server" do
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_v1_spec.rb
similarity index 50%
copy from spec/unit/api_client_spec.rb
copy to spec/unit/api_client_v1_spec.rb
index 7668e31..17aba8c 100644
--- a/spec/unit/api_client_spec.rb
+++ b/spec/unit/api_client_v1_spec.rb
@@ -18,12 +18,12 @@
 
 require 'spec_helper'
 
-require 'chef/api_client'
+require 'chef/api_client_v1'
 require 'tempfile'
 
-describe Chef::ApiClient do
+describe Chef::ApiClientV1 do
   before(:each) do
-    @client = Chef::ApiClient.new
+    @client = Chef::ApiClientV1.new
   end
 
   it "has a name attribute" do
@@ -53,6 +53,20 @@ describe Chef::ApiClient do
     expect { @client.admin(Hash.new) }.to raise_error(ArgumentError)
   end
 
+  it "has an create_key flag attribute" do
+    @client.create_key(true)
+    expect(@client.create_key).to be_truthy
+  end
+
+  it "create_key defaults to false" do
+    expect(@client.create_key).to be_falsey
+  end
+
+  it "allows only boolean values for the create_key flag" do
+    expect { @client.create_key(false) }.not_to raise_error
+    expect { @client.create_key(Hash.new) }.to raise_error(ArgumentError)
+  end
+
   it "has a 'validator' flag attribute" do
     @client.validator(true)
     expect(@client.validator).to be_truthy
@@ -115,6 +129,12 @@ describe Chef::ApiClient do
       expect(@json).to include(%q{"validator":false})
     end
 
+    it "includes the 'create_key' flag when present" do
+      @client.create_key(true)
+      @json = @client.to_json
+      expect(@json).to include(%q{"create_key":true})
+    end
+
     it "includes the private key when present" do
       @client.private_key("monkeypants")
       expect(@client.to_json).to include(%q{"private_key":"monkeypants"})
@@ -124,26 +144,26 @@ describe Chef::ApiClient do
       expect(@json).not_to include("private_key")
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) { @client }
     end
   end
 
   describe "when deserializing from JSON (string) using ApiClient#from_json" do
     let(:client_string) do
-      "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true}"
+      "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true,\"create_key\":true}"
     end
 
     let(:client) do
-      Chef::ApiClient.from_json(client_string)
+      Chef::ApiClientV1.from_json(client_string)
     end
 
     it "does not require a 'json_class' string" do
       expect(Chef::JSONCompat.parse(client_string)["json_class"]).to eq(nil)
     end
 
-    it "should deserialize to a Chef::ApiClient object" do
-      expect(client).to be_a_kind_of(Chef::ApiClient)
+    it "should deserialize to a Chef::ApiClientV1 object" do
+      expect(client).to be_a_kind_of(Chef::ApiClientV1)
     end
 
     it "preserves the name" do
@@ -158,6 +178,10 @@ describe Chef::ApiClient do
       expect(client.admin).to be_truthy
     end
 
+    it "preserves the create_key status" do
+      expect(client.create_key).to be_truthy
+    end
+
     it "preserves the 'validator' status" do
       expect(client.validator).to be_truthy
     end
@@ -167,7 +191,7 @@ describe Chef::ApiClient do
     end
   end
 
-  describe "when deserializing from JSON (hash) using JSONCompat#from_json" do
+  describe "when deserializing from JSON (hash) using ApiClientV1#from_json" do
     let(:client_hash) do
       {
         "name" => "black",
@@ -175,16 +199,16 @@ describe Chef::ApiClient do
         "private_key" => "monkeypants",
         "admin" => true,
         "validator" => true,
-        "json_class" => "Chef::ApiClient"
+        "create_key" => true
       }
     end
 
     let(:client) do
-      Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(client_hash))
+      Chef::ApiClientV1.from_json(Chef::JSONCompat.to_json(client_hash))
     end
 
-    it "should deserialize to a Chef::ApiClient object" do
-      expect(client).to be_a_kind_of(Chef::ApiClient)
+    it "should deserialize to a Chef::ApiClientV1 object" do
+      expect(client).to be_a_kind_of(Chef::ApiClientV1)
     end
 
     it "preserves the name" do
@@ -199,6 +223,10 @@ describe Chef::ApiClient do
       expect(client.admin).to be_truthy
     end
 
+    it "preserves the create_key status" do
+      expect(client.create_key).to be_truthy
+    end
+
     it "preserves the 'validator' status" do
       expect(client.validator).to be_truthy
     end
@@ -214,22 +242,23 @@ describe Chef::ApiClient do
 
     before(:each) do
       client = {
-      "name" => "black",
-      "clientname" => "black",
-      "public_key" => "crowes",
-      "private_key" => "monkeypants",
-      "admin" => true,
-      "validator" => true,
-      "json_class" => "Chef::ApiClient"
+        "name" => "black",
+        "clientname" => "black",
+        "public_key" => "crowes",
+        "private_key" => "monkeypants",
+        "admin" => true,
+        "create_key" => true,
+        "validator" => true
       }
-      @http_client = double("Chef::REST mock")
-      allow(Chef::REST).to receive(:new).and_return(@http_client)
+
+      @http_client = double("Chef::ServerAPI mock")
+      allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
       expect(@http_client).to receive(:get).with("clients/black").and_return(client)
-      @client = Chef::ApiClient.load(client['name'])
+      @client = Chef::ApiClientV1.load(client['name'])
     end
 
-    it "should deserialize to a Chef::ApiClient object" do
-      expect(@client).to be_a_kind_of(Chef::ApiClient)
+    it "should deserialize to a Chef::ApiClientV1 object" do
+      expect(@client).to be_a_kind_of(Chef::ApiClientV1)
     end
 
     it "preserves the name" do
@@ -244,6 +273,10 @@ describe Chef::ApiClient do
       expect(@client.admin).to be_a_kind_of(TrueClass)
     end
 
+    it "preserves the create_key status" do
+      expect(@client.create_key).to be_a_kind_of(TrueClass)
+    end
+
     it "preserves the 'validator' status" do
       expect(@client.validator).to be_a_kind_of(TrueClass)
     end
@@ -269,18 +302,13 @@ describe Chef::ApiClient do
       File.open(Chef::Config[:client_key], "r") {|f| f.read.chomp }
     end
 
-    it "has an HTTP client configured with default credentials" do
-      expect(@client.http_api).to be_a_kind_of(Chef::REST)
-      expect(@client.http_api.client_name).to eq("silent-bob")
-      expect(@client.http_api.signing_key.to_s).to eq(private_key_data)
-    end
   end
 
 
   describe "when requesting a new key" do
     before do
-      @http_client = double("Chef::REST mock")
-      allow(Chef::REST).to receive(:new).and_return(@http_client)
+      @http_client = double("Chef::ServerAPI mock")
+      allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
     end
 
     context "and the client does not exist on the server" do
@@ -292,57 +320,138 @@ describe Chef::ApiClient do
       end
 
       it "raises a 404 error" do
-        expect { Chef::ApiClient.reregister("lost-my-key") }.to raise_error(Net::HTTPServerException)
+        expect { Chef::ApiClientV1.reregister("lost-my-key") }.to raise_error(Net::HTTPServerException)
       end
     end
+  end
 
-    context "and the client exists" do
-      before do
-        @api_client_without_key = Chef::ApiClient.new
-        @api_client_without_key.name("lost-my-key")
-        expect(@http_client).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key)
-      end
+  describe "Versioned API Interactions" do
+    let(:response_406) { OpenStruct.new(:code => '406') }
+    let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+    let(:payload)  {
+      {
+        :name => "some_name",
+        :validator => true,
+        :admin => true
+      }
+    }
 
+    before do
+      @client = Chef::ApiClientV1.new
+      allow(@client).to receive(:chef_rest_v0).and_return(double('chef rest root v0 object'))
+      allow(@client).to receive(:chef_rest_v1).and_return(double('chef rest root v1 object'))
+      @client.name "some_name"
+      @client.validator true
+      @client.admin true
+    end
+
+    describe "create" do
+
+      # from spec/support/shared/unit/user_and_client_shared.rb
+      it_should_behave_like "user or client create" do
+        let(:object)  { @client }
+        let(:error)   { Chef::Exceptions::InvalidClientAttribute }
+        let(:rest_v0) { @client.chef_rest_v0 }
+        let(:rest_v1) { @client.chef_rest_v1 }
+        let(:url)     { "clients" }
+      end
 
-      context "and the client exists on a Chef 11-like server" do
-        before do
-          @api_client_with_key = Chef::ApiClient.new
-          @api_client_with_key.name("lost-my-key")
-          @api_client_with_key.private_key("the new private key")
-          expect(@http_client).to receive(:put).
-            with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true).
-            and_return(@api_client_with_key)
+      context "when API V1 is not supported by the server" do
+        # from spec/support/shared/unit/api_versioning.rb
+        it_should_behave_like "version handling" do
+          let(:object)    { @client }
+          let(:method)    { :create }
+          let(:http_verb) { :post }
+          let(:rest_v1)   { @client.chef_rest_v1 }
         end
+      end
+
+    end # create
+
+    describe "update" do
+      context "when a valid client is defined" do
+
+        shared_examples_for "client updating" do
+          it "updates the client" do
+            expect(rest). to receive(:put).with("clients/some_name", payload).and_return(payload)
+            @client.update
+          end
+
+          context "when only the name field exists" do
+
+            before do
+              # needed since there is no way to set to nil via code
+              @client.instance_variable_set(:@validator, nil)
+              @client.instance_variable_set(:@admin, nil)
+            end
+
+            after do
+              @client.validator true
+              @client.admin true
+            end
+
+            it "updates the client with only the name" do
+              expect(rest). to receive(:put).with("clients/some_name", {:name => "some_name"}).and_return({:name => "some_name"})
+              @client.update
+            end
+          end
 
-        it "returns an ApiClient with a private key" do
-          response = Chef::ApiClient.reregister("lost-my-key")
-          # no sane == method for ApiClient :'(
-          expect(response).to eq(@api_client_without_key)
-          expect(response.private_key).to eq("the new private key")
-          expect(response.name).to eq("lost-my-key")
-          expect(response.admin).to be_falsey
         end
-      end
 
-      context "and the client exists on a Chef 10-like server" do
-        before do
-          @api_client_with_key = {"name" => "lost-my-key", "private_key" => "the new private key"}
-          expect(@http_client).to receive(:put).
-            with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true).
-            and_return(@api_client_with_key)
+        context "when API V1 is supported by the server" do
+
+          it_should_behave_like "client updating" do
+            let(:rest) { @client.chef_rest_v1 }
+          end
+
+        end # when API V1 is supported by the server
+
+        context "when API V1 is not supported by the server" do
+          context "when no version is supported" do
+            # from spec/support/shared/unit/api_versioning.rb
+            it_should_behave_like "version handling" do
+              let(:object)    { @client }
+              let(:method)    { :create }
+              let(:http_verb) { :post }
+              let(:rest_v1)   { @client.chef_rest_v1 }
+            end
+          end # when no version is supported
+
+          context "when API V0 is supported" do
+
+            before do
+              allow(@client.chef_rest_v1).to receive(:put).and_raise(exception_406)
+              allow(@client).to receive(:server_client_api_version_intersection).and_return([0])
+            end
+
+            it_should_behave_like "client updating" do
+              let(:rest) { @client.chef_rest_v0 }
+            end
+
+          end
+
+        end # when API V1 is not supported by the server
+      end # when a valid client is defined
+    end # update
+
+    # DEPRECATION
+    # This can be removed after API V0 support is gone
+    describe "reregister" do
+      context "when server API V0 is valid on the Chef Server receiving the request" do
+        it "creates a new object via the API" do
+          expect(@client.chef_rest_v0).to receive(:put).with("clients/#{@client.name}", payload.merge({:private_key => true})).and_return({})
+          @client.reregister
         end
+      end # when server API V0 is valid on the Chef Server receiving the request
 
-        it "returns an ApiClient with a private key" do
-          response = Chef::ApiClient.reregister("lost-my-key")
-          # no sane == method for ApiClient :'(
-          expect(response).to eq(@api_client_without_key)
-          expect(response.private_key).to eq("the new private key")
-          expect(response.name).to eq("lost-my-key")
-          expect(response.admin).to be_falsey
-          expect(response.validator).to be_falsey
+      context "when server API V0 is not supported by the Chef Server" do
+        # from spec/support/shared/unit/api_versioning.rb
+        it_should_behave_like "user and client reregister" do
+          let(:object)    { @client }
+          let(:rest_v0)   { @client.chef_rest_v0 }
         end
-      end
+      end # when server API V0 is not supported by the Chef Server
+    end # reregister
 
-    end
   end
 end
diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb
index c753ca0..727536f 100644
--- a/spec/unit/application/client_spec.rb
+++ b/spec/unit/application/client_spec.rb
@@ -47,6 +47,19 @@ describe Chef::Application::Client, "reconfigure" do
       expect(app).to receive(:set_specific_recipes).and_return(true)
       app.reconfigure
     end
+
+    context "when given a named_run_list" do
+
+      before do
+        ARGV.replace( %w[ --named-run-list arglebargle-example ] )
+        app.reconfigure
+      end
+
+      it "sets named_run_list in Chef::Config" do
+        expect(Chef::Config[:named_run_list]).to eq("arglebargle-example")
+      end
+
+    end
   end
 
   describe "when configured to not fork the client process" do
@@ -60,7 +73,7 @@ describe Chef::Application::Client, "reconfigure" do
     context "when interval is given" do
       before do
         Chef::Config[:interval] = 600
-        allow(Chef::Platform).to receive(:windows?).and_return(false)
+        allow(ChefConfig).to receive(:windows?).and_return(false)
       end
 
       it "should terminate with message" do
@@ -77,7 +90,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
     context "when interval is given on windows" do
       before do
         Chef::Config[:interval] = 600
-        allow(Chef::Platform).to receive(:windows?).and_return(true)
+        allow(ChefConfig).to receive(:windows?).and_return(true)
       end
 
       it "should not terminate" do
@@ -165,11 +178,6 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
       before do
         allow(Chef::Log).to receive(:warn)
       end
-
-      it "emits a warning that audit mode is an experimental feature" do
-        expect(Chef::Log).to receive(:warn).with(/Audit mode is an experimental feature/)
-        app.reconfigure
-      end
     end
 
     shared_examples "unrecognized setting" do
@@ -242,7 +250,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
     end
 
     it "should throw an exception" do
-      expect { @app.reconfigure }.to raise_error
+      expect { app.reconfigure }.to raise_error(Chef::Exceptions::PIDFileLockfileMatch)
     end
   end
 end
@@ -280,9 +288,9 @@ describe Chef::Application::Client, "configure_chef" do
     ARGV.replace(@original_argv)
   end
 
-  it "should set the colored output to false by default on windows and true otherwise" do
+  it "should set the colored output to true by default on windows and true on all other platforms as well" do
     if windows?
-      expect(Chef::Config[:color]).to be_falsey
+      expect(Chef::Config[:color]).to be_truthy
     else
       expect(Chef::Config[:color]).to be_truthy
     end
diff --git a/spec/unit/application/knife_spec.rb b/spec/unit/application/knife_spec.rb
index 3c215ea..8894e86 100644
--- a/spec/unit/application/knife_spec.rb
+++ b/spec/unit/application/knife_spec.rb
@@ -70,13 +70,13 @@ describe Chef::Application::Knife do
     end
   end
 
-  it "should set the colored output to false by default on windows and true otherwise" do
+  it "should set the colored output to true by default on windows and true on all other platforms as well" do
     with_argv(*%w{noop knife command}) do
       expect(@knife).to receive(:exit).with(0)
       @knife.run
     end
     if windows?
-      expect(Chef::Config[:color]).to be_falsey
+      expect(Chef::Config[:color]).to be_truthy
     else
       expect(Chef::Config[:color]).to be_truthy
     end
diff --git a/spec/unit/application/solo_spec.rb b/spec/unit/application/solo_spec.rb
index 1785ecf..7013bfa 100644
--- a/spec/unit/application/solo_spec.rb
+++ b/spec/unit/application/solo_spec.rb
@@ -106,7 +106,8 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
     describe "when the recipe_url configuration option is specified" do
       let(:tarfile) { StringIO.new("remote_tarball_content") }
       let(:target_file) { StringIO.new }
-
+      let(:shellout) { double(run_command: nil, error!: nil, stdout: '') }
+     
       before do
         Chef::Config[:cookbook_path] = "#{Dir.tmpdir}/chef-solo/cookbooks"
         Chef::Config[:recipe_url] = "http://junglist.gen.nz/recipes.tgz"
@@ -117,7 +118,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
         allow(app).to receive(:open).with("http://junglist.gen.nz/recipes.tgz").and_yield(tarfile)
         allow(File).to receive(:open).with("#{Dir.tmpdir}/chef-solo/recipes.tgz", "wb").and_yield(target_file)
 
-        allow(Chef::Mixin::Command).to receive(:run_command).and_return(true)
+        allow(Mixlib::ShellOut).to receive(:new).and_return(shellout)
       end
 
       it "should create the recipes path based on the parent of the cookbook path" do
@@ -136,7 +137,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
       end
 
       it "should untar the target file to the parent of the cookbook path" do
-        expect(Chef::Mixin::Command).to receive(:run_command).with({:command => "tar zxvf #{Dir.tmpdir}/chef-solo/recipes.tgz -C #{Dir.tmpdir}/chef-solo"}).and_return(true)
+        expect(Mixlib::ShellOut).to receive(:new).with("tar zxvf #{Dir.tmpdir}/chef-solo/recipes.tgz -C #{Dir.tmpdir}/chef-solo")  
         app.reconfigure
       end
     end
diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb
index f5a2c72..20b7e3a 100644
--- a/spec/unit/application_spec.rb
+++ b/spec/unit/application_spec.rb
@@ -39,7 +39,6 @@ describe Chef::Application do
       @app = Chef::Application.new
       allow(@app).to receive(:configure_chef).and_return(true)
       allow(@app).to receive(:configure_logging).and_return(true)
-      allow(@app).to receive(:configure_proxy_environment_variables).and_return(true)
     end
 
     it "should configure chef" do
@@ -52,11 +51,6 @@ describe Chef::Application do
       @app.reconfigure
     end
 
-    it "should configure environment variables" do
-      expect(@app).to receive(:configure_proxy_environment_variables).and_return(true)
-      @app.reconfigure
-    end
-
     it 'should not receive set_specific_recipes' do
       expect(@app).to_not receive(:set_specific_recipes)
       @app.reconfigure
@@ -101,6 +95,7 @@ describe Chef::Application do
       @app = Chef::Application.new
       #Chef::Config.stub(:merge!).and_return(true)
       allow(@app).to receive(:parse_options).and_return(true)
+      expect(Chef::Config).to receive(:export_proxies).and_return(true)
     end
 
     it "should parse the commandline options" do
@@ -245,181 +240,6 @@ describe Chef::Application do
     end
   end
 
-  describe "when configuring environment variables" do
-    def configure_proxy_environment_variables_stubs
-      allow(@app).to receive(:configure_http_proxy).and_return(true)
-      allow(@app).to receive(:configure_https_proxy).and_return(true)
-      allow(@app).to receive(:configure_ftp_proxy).and_return(true)
-      allow(@app).to receive(:configure_no_proxy).and_return(true)
-    end
-
-    shared_examples_for "setting ENV['http_proxy']" do
-      before do
-        Chef::Config[:http_proxy] = http_proxy
-      end
-
-      it "should set ENV['http_proxy']" do
-        @app.configure_proxy_environment_variables
-        expect(@env['http_proxy']).to eq("#{scheme}://#{address}:#{port}")
-      end
-
-      it "should set ENV['HTTP_PROXY']" do
-        @app.configure_proxy_environment_variables
-        expect(@env['HTTP_PROXY']).to eq("#{scheme}://#{address}:#{port}")
-      end
-
-      describe "when Chef::Config[:http_proxy_user] is set" do
-        before do
-          Chef::Config[:http_proxy_user] = "username"
-        end
-
-        it "should set ENV['http_proxy'] with the username" do
-          @app.configure_proxy_environment_variables
-          expect(@env['http_proxy']).to eq("#{scheme}://username@#{address}:#{port}")
-          expect(@env['HTTP_PROXY']).to eq("#{scheme}://username@#{address}:#{port}")
-        end
-
-        context "when :http_proxy_user contains '@' and/or ':'" do
-          before do
-            Chef::Config[:http_proxy_user] = "my:usern at me"
-          end
-
-          it "should set ENV['http_proxy'] with the escaped username" do
-            @app.configure_proxy_environment_variables
-            expect(@env['http_proxy']).to eq("#{scheme}://my%3Ausern%40me@#{address}:#{port}")
-            expect(@env['HTTP_PROXY']).to eq("#{scheme}://my%3Ausern%40me@#{address}:#{port}")
-          end
-        end
-
-        describe "when Chef::Config[:http_proxy_pass] is set" do
-          before do
-            Chef::Config[:http_proxy_pass] = "password"
-          end
-
-          it "should set ENV['http_proxy'] with the password" do
-            @app.configure_proxy_environment_variables
-            expect(@env['http_proxy']).to eq("#{scheme}://username:password@#{address}:#{port}")
-            expect(@env['HTTP_PROXY']).to eq("#{scheme}://username:password@#{address}:#{port}")
-          end
-
-          context "when :http_proxy_pass contains '@' and/or ':'" do
-            before do
-              Chef::Config[:http_proxy_pass] = ":P at ssword101"
-            end
-
-            it "should set ENV['http_proxy'] with the escaped password" do
-              @app.configure_proxy_environment_variables
-              expect(@env['http_proxy']).to eq("#{scheme}://username:%3AP%40ssword101@#{address}:#{port}")
-              expect(@env['HTTP_PROXY']).to eq("#{scheme}://username:%3AP%40ssword101@#{address}:#{port}")
-            end
-          end
-        end
-      end
-
-      describe "when Chef::Config[:http_proxy_pass] is set (but not Chef::Config[:http_proxy_user])" do
-        before do
-          Chef::Config[:http_proxy_user] = nil
-          Chef::Config[:http_proxy_pass] = "password"
-        end
-
-        it "should set ENV['http_proxy']" do
-          @app.configure_proxy_environment_variables
-          expect(@env['http_proxy']).to eq("#{scheme}://#{address}:#{port}")
-          expect(@env['HTTP_PROXY']).to eq("#{scheme}://#{address}:#{port}")
-        end
-      end
-    end
-
-    describe "when configuring ENV['http_proxy']" do
-      before do
-        @env = {}
-        allow(@app).to receive(:env).and_return(@env)
-
-        allow(@app).to receive(:configure_https_proxy).and_return(true)
-        allow(@app).to receive(:configure_ftp_proxy).and_return(true)
-        allow(@app).to receive(:configure_no_proxy).and_return(true)
-      end
-
-      describe "when Chef::Config[:http_proxy] is not set" do
-        before do
-          Chef::Config[:http_proxy] = nil
-        end
-
-        it "should not set ENV['http_proxy']" do
-          @app.configure_proxy_environment_variables
-          expect(@env).to eq({})
-        end
-      end
-
-      describe "when Chef::Config[:http_proxy] is set" do
-        context "when given an FQDN" do
-          let(:scheme) { "http" }
-          let(:address) { "proxy.example.org" }
-          let(:port) { 8080 }
-          let(:http_proxy) { "#{scheme}://#{address}:#{port}" }
-
-          it_should_behave_like "setting ENV['http_proxy']"
-        end
-
-        context "when given an HTTPS URL" do
-          let(:scheme) { "https" }
-          let(:address) { "proxy.example.org" }
-          let(:port) { 8080 }
-          let(:http_proxy) { "#{scheme}://#{address}:#{port}" }
-
-          it_should_behave_like "setting ENV['http_proxy']"
-        end
-
-        context "when given an IP" do
-          let(:scheme) { "http" }
-          let(:address) { "127.0.0.1" }
-          let(:port) { 22 }
-          let(:http_proxy) { "#{scheme}://#{address}:#{port}" }
-
-          it_should_behave_like "setting ENV['http_proxy']"
-        end
-
-        context "when given an IPv6" do
-          let(:scheme) { "http" }
-          let(:address) { "[2001:db8::1]" }
-          let(:port) { 80 }
-          let(:http_proxy) { "#{scheme}://#{address}:#{port}" }
-
-          it_should_behave_like "setting ENV['http_proxy']"
-        end
-
-        context "when given without including http://" do
-          let(:scheme) { "http" }
-          let(:address) { "proxy.example.org" }
-          let(:port) { 8181 }
-          let(:http_proxy) { "#{address}:#{port}" }
-
-          it_should_behave_like "setting ENV['http_proxy']"
-        end
-
-        context "when given the full proxy in :http_proxy only" do
-          before do
-            Chef::Config[:http_proxy] = "http://username:password@proxy.example.org:2222"
-            Chef::Config[:http_proxy_user] = nil
-            Chef::Config[:http_proxy_pass] = nil
-          end
-
-          it "should set ENV['http_proxy']" do
-            @app.configure_proxy_environment_variables
-            expect(@env['http_proxy']).to eq(Chef::Config[:http_proxy])
-          end
-        end
-
-        context "when the config options aren't URI compliant" do
-          it "raises Chef::Exceptions::BadProxyURI" do
-            Chef::Config[:http_proxy] = "http://proxy.bad_example.org/:8080"
-            expect { @app.configure_proxy_environment_variables }.to raise_error(Chef::Exceptions::BadProxyURI)
-          end
-        end
-      end
-    end
-  end
-
   describe "class method: fatal!" do
     before do
       allow(STDERR).to receive(:puts).with("FATAL: blah").and_return(true)
diff --git a/spec/unit/audit/audit_reporter_spec.rb b/spec/unit/audit/audit_reporter_spec.rb
index 4bf8895..46c2a96 100644
--- a/spec/unit/audit/audit_reporter_spec.rb
+++ b/spec/unit/audit/audit_reporter_spec.rb
@@ -88,6 +88,29 @@ describe Chef::Audit::AuditReporter do
         reporter.run_completed(node)
       end
 
+      context "when audit phase failed" do
+
+        let(:audit_error) { double("AuditError", :class => "Chef::Exceptions::AuditError",
+          :message => "Audit phase failed with error message: derpderpderp",
+          :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) }
+
+          before do
+            reporter.instance_variable_set(:@audit_phase_error, audit_error)
+          end
+
+        it "reports an error" do
+          reporter.run_completed(node)
+          expect(run_data).to have_key(:error)
+          expect(run_data).to have_key(:error)
+          expect(run_data[:error]).to eq <<-EOM.strip!
+Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp
+/path/recipe.rb:57
+/path/library.rb:106
+EOM
+        end
+
+      end
+
       context "when unable to post to server" do
 
         let(:error) do
@@ -215,9 +238,13 @@ describe Chef::Audit::AuditReporter do
     let(:audit_data) { Chef::Audit::AuditData.new(node.name, run_id) }
     let(:run_data) { audit_data.to_hash }
 
-    let(:error) { double("AuditError", :class => "Chef::Exception::AuditError",
-      :message => "Well that certainly didn't work",
-      :backtrace => ["line 0", "line 1", "line 2"]) }
+    let(:audit_error) { double("AuditError", :class => "Chef::Exceptions::AuditError",
+      :message => "Audit phase failed with error message: derpderpderp",
+      :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) }
+
+    let(:run_error) { double("RunError", :class => "Chef::Exceptions::RunError",
+      :message => "This error shouldn't be reported.",
+      :backtrace => ["fix it", "fix it", "fix it"]) }
 
     before do
       allow(reporter).to receive(:auditing_enabled?).and_return(true)
@@ -226,15 +253,32 @@ describe Chef::Audit::AuditReporter do
       allow(audit_data).to receive(:to_hash).and_return(run_data)
     end
 
-    it "adds the error information to the reported data" do
-      expect(rest).to receive(:create_url)
-      expect(rest).to receive(:post)
-      reporter.run_failed(error)
-      expect(run_data).to have_key(:error)
-      expect(run_data[:error]).to eq "Chef::Exception::AuditError: Well that certainly didn't work\n" +
-        "line 0\nline 1\nline 2"
+    context "when no prior exception is stored" do
+      it "reports no error" do
+        expect(rest).to receive(:create_url)
+        expect(rest).to receive(:post)
+        reporter.run_failed(run_error)
+        expect(run_data).to_not have_key(:error)
+      end
     end
 
+    context "when some prior exception is stored" do
+      before do
+        reporter.instance_variable_set(:@audit_phase_error, audit_error)
+      end
+
+      it "reports the prior error" do
+        expect(rest).to receive(:create_url)
+        expect(rest).to receive(:post)
+        reporter.run_failed(run_error)
+        expect(run_data).to have_key(:error)
+        expect(run_data[:error]).to eq <<-EOM.strip!
+Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp
+/path/recipe.rb:57
+/path/library.rb:106
+EOM
+      end
+    end
   end
 
   shared_context "audit data" do
@@ -270,14 +314,14 @@ describe Chef::Audit::AuditReporter do
 
     it "notifies audit phase finished to debug log" do
       expect(Chef::Log).to receive(:debug).with(/Audit Reporter completed/)
-      reporter.audit_phase_complete
+      reporter.audit_phase_complete("Output from audit mode")
     end
 
     it "collects audit data" do
       ordered_control_groups.each do |_name, group|
         expect(audit_data).to receive(:add_control_group).with(group)
       end
-      reporter.audit_phase_complete
+      reporter.audit_phase_complete("Output from audit mode")
     end
   end
 
@@ -288,14 +332,14 @@ describe Chef::Audit::AuditReporter do
 
     it "notifies audit phase failed to debug log" do
       expect(Chef::Log).to receive(:debug).with(/Audit Reporter failed/)
-      reporter.audit_phase_failed(error)
+      reporter.audit_phase_failed(error, "Output from audit mode")
     end
 
     it "collects audit data" do
       ordered_control_groups.each do |_name, group|
         expect(audit_data).to receive(:add_control_group).with(group)
       end
-      reporter.audit_phase_failed(error)
+      reporter.audit_phase_failed(error, "Output from audit mode")
     end
   end
 
diff --git a/spec/unit/audit/logger_spec.rb b/spec/unit/audit/logger_spec.rb
new file mode 100644
index 0000000..9dd9ce2
--- /dev/null
+++ b/spec/unit/audit/logger_spec.rb
@@ -0,0 +1,42 @@
+#
+# Copyright:: Copyright (c) 2014 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 'spec_helper'
+
+describe Chef::Audit::Logger do
+
+  before(:each) do
+    Chef::Audit::Logger.instance_variable_set(:@buffer, nil)
+  end
+
+  it 'calling puts creates @buffer and adds the message' do
+    Chef::Audit::Logger.puts("Output message")
+    expect(Chef::Audit::Logger.read_buffer).to eq("Output message\n")
+  end
+
+  it 'calling puts multiple times adds to the message' do
+    Chef::Audit::Logger.puts("Output message")
+    Chef::Audit::Logger.puts("Output message")
+    Chef::Audit::Logger.puts("Output message")
+    expect(Chef::Audit::Logger.read_buffer).to eq("Output message\nOutput message\nOutput message\n")
+  end
+
+  it 'calling it before @buffer is set returns an empty string' do
+    expect(Chef::Audit::Logger.read_buffer).to eq("")
+  end
+
+end
diff --git a/spec/unit/audit/runner_spec.rb b/spec/unit/audit/runner_spec.rb
index 0bd4c18..1de0242 100644
--- a/spec/unit/audit/runner_spec.rb
+++ b/spec/unit/audit/runner_spec.rb
@@ -68,8 +68,8 @@ describe Chef::Audit::Runner do
         in_sub_process do
           runner.send(:setup)
 
-          expect(RSpec.configuration.output_stream).to eq(log_location)
-          expect(RSpec.configuration.error_stream).to eq(log_location)
+          expect(RSpec.configuration.output_stream).to eq(Chef::Audit::Logger)
+          expect(RSpec.configuration.error_stream).to eq(Chef::Audit::Logger)
 
           expect(RSpec.configuration.formatters.size).to eq(2)
           expect(RSpec.configuration.formatters).to include(instance_of(Chef::Audit::AuditEventProxy))
diff --git a/spec/unit/chef_class_spec.rb b/spec/unit/chef_class_spec.rb
index 2528246..f1b8775 100644
--- a/spec/unit/chef_class_spec.rb
+++ b/spec/unit/chef_class_spec.rb
@@ -46,10 +46,6 @@ describe "Chef class" do
     Chef.set_provider_priority_map(provider_priority_map)
   end
 
-  after do
-    Chef.reset!
-  end
-
   context "priority maps" do
     context "#get_provider_priority_array" do
       it "should use the current node to get the right priority_map" do
@@ -88,4 +84,27 @@ describe "Chef class" do
       expect(Chef.node).to eql(node)
     end
   end
+
+  context '#event_handler' do
+    it 'adds a new handler' do
+      x = 1
+      Chef.event_handler do
+        on :converge_start do
+          x = 2
+        end
+      end
+      expect(Chef::Config[:event_handlers]).to_not be_empty
+      Chef::Config[:event_handlers].first.send(:converge_start)
+      expect(x).to eq(2)
+    end
+
+    it 'raise error if unknown event type is passed' do
+      expect do
+        Chef.event_handler do
+          on :yolo do
+          end
+        end
+      end.to raise_error(Chef::Exceptions::InvalidEventType)
+    end
+  end
 end
diff --git a/spec/unit/chef_fs/file_pattern_spec.rb b/spec/unit/chef_fs/file_pattern_spec.rb
index a9f06e8..ed5f314 100644
--- a/spec/unit/chef_fs/file_pattern_spec.rb
+++ b/spec/unit/chef_fs/file_pattern_spec.rb
@@ -157,7 +157,7 @@ describe Chef::ChefFS::FilePattern do
     end
   end
 
-  context 'with simple pattern "a\*\b"', :pending => (Chef::Platform.windows?) do
+  context 'with simple pattern "a\*\b"', :skip => (Chef::Platform.windows?) do
     let(:pattern) { Chef::ChefFS::FilePattern.new('a\*\b') }
     it 'match?' do
       expect(pattern.match?('a*b')).to be_truthy
@@ -264,7 +264,7 @@ describe Chef::ChefFS::FilePattern do
     end
   end
 
-  context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :pending => (Chef::Platform.windows?) do
+  context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :skip => (Chef::Platform.windows?) do
     let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d[a-z][0-9]f/ghi') }
     it 'match?' do
       expect(pattern.match?('/abc/de1f/ghi')).to be_truthy
@@ -352,11 +352,7 @@ describe Chef::ChefFS::FilePattern do
       expect(pattern.could_match_children?('/abc/def/ghi')).to be_truthy
       expect(pattern.could_match_children?('abc')).to be_falsey
     end
-    it 'could_match_children? /abc** returns false for /xyz' do
-      pending 'Make could_match_children? more rigorous'
-      # At the moment, we return false for this, but in the end it would be nice to return true:
-      expect(pattern.could_match_children?('/xyz')).to be_falsey
-    end
+
     it 'exact_child_name_under' do
       expect(pattern.exact_child_name_under('/')).to eq(nil)
       expect(pattern.exact_child_name_under('/abc')).to eq(nil)
@@ -440,14 +436,6 @@ describe Chef::ChefFS::FilePattern do
       expect(p('/.').exact_path).to eq('/')
       expect(p('/.').match?('/')).to be_truthy
     end
-    it 'handles dot by itself', :pending => "decide what to do with dot by itself" do
-      expect(p('.').normalized_pattern).to eq('.')
-      expect(p('.').exact_path).to eq('.')
-      expect(p('.').match?('.')).to be_truthy
-      expect(p('./').normalized_pattern).to eq('.')
-      expect(p('./').exact_path).to eq('.')
-      expect(p('./').match?('.')).to be_truthy
-    end
     it 'handles dotdot' do
       expect(p('abc/../def').normalized_pattern).to eq('def')
       expect(p('abc/../def').exact_path).to eq('def')
diff --git a/spec/unit/knife/client_list_spec.rb b/spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb
similarity index 59%
copy from spec/unit/knife/client_list_spec.rb
copy to spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb
index eff01da..94a636f 100644
--- a/spec/unit/knife/client_list_spec.rb
+++ b/spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
-# Copyright:: Copyright (c) 2011 Thomas Bishop
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,18 +17,18 @@
 #
 
 require 'spec_helper'
+require 'chef/chef_fs/file_system/cookbook_subdir'
 
-describe Chef::Knife::ClientList do
-  before(:each) do
-    @knife = Chef::Knife::ClientList.new
-    @knife.name_args = [ 'adam' ]
+describe Chef::ChefFS::FileSystem::CookbookSubdir do
+  let(:root) do
+    Chef::ChefFS::FileSystem::BaseFSDir.new('', nil)
   end
 
-  describe 'run' do
-    it 'should list the clients' do
-      expect(Chef::ApiClient).to receive(:list)
-      expect(@knife).to receive(:format_list_for_display)
-      @knife.run
-    end
+  let(:cookbook_subdir) do
+    Chef::ChefFS::FileSystem::CookbookSubdir.new('test', root, false, true)
+  end
+
+  it 'can get child' do
+    cookbook_subdir.child('test')
   end
 end
diff --git a/spec/unit/chef_fs/path_util_spec.rb b/spec/unit/chef_fs/path_util_spec.rb
new file mode 100644
index 0000000..42eb126
--- /dev/null
+++ b/spec/unit/chef_fs/path_util_spec.rb
@@ -0,0 +1,108 @@
+#
+# Author:: Kartik Null Cating-Subramanian (<ksubramanian at chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, 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 'spec_helper'
+require 'chef/chef_fs/path_utils'
+
+describe Chef::ChefFS::PathUtils do
+  context 'invoking join' do
+    it 'joins well-behaved distinct path elements' do
+      expect(Chef::ChefFS::PathUtils.join('a', 'b', 'c')).to eq('a/b/c')
+    end
+
+    it 'strips extraneous slashes in the middle of paths' do
+      expect(Chef::ChefFS::PathUtils.join('a/', '/b', '/c/')).to eq('a/b/c')
+      expect(Chef::ChefFS::PathUtils.join('a/', '/b', '///c/')).to eq('a/b/c')
+    end
+
+    it 'preserves the whether the first element was absolute or not' do
+      expect(Chef::ChefFS::PathUtils.join('/a/', '/b', 'c/')).to eq('/a/b/c')
+      expect(Chef::ChefFS::PathUtils.join('///a/', '/b', 'c/')).to eq('/a/b/c')
+    end
+  end
+
+  context 'invoking is_absolute?' do
+    it 'confirms that paths starting with / are absolute' do
+      expect(Chef::ChefFS::PathUtils.is_absolute?('/foo/bar/baz')).to be true
+      expect(Chef::ChefFS::PathUtils.is_absolute?('/foo')).to be true
+    end
+
+    it 'confirms that paths starting with // are absolute even though that looks like some windows network path' do
+      expect(Chef::ChefFS::PathUtils.is_absolute?('//foo/bar/baz')).to be true
+    end
+
+    it 'confirms that root is indeed absolute' do
+      expect(Chef::ChefFS::PathUtils.is_absolute?('/')).to be true
+    end
+
+    it 'confirms that paths starting without / are relative' do
+      expect(Chef::ChefFS::PathUtils.is_absolute?('foo/bar/baz')).to be false
+      expect(Chef::ChefFS::PathUtils.is_absolute?('a')).to be false
+    end
+
+    it 'returns false for an empty path.' do
+      expect(Chef::ChefFS::PathUtils.is_absolute?('')).to be false
+    end
+  end
+
+  context 'invoking realest_path' do
+    let(:good_path) { File.dirname(__FILE__) }
+    let(:parent_path) { File.dirname(good_path) }
+
+    it 'handles paths with no wildcards or globs' do
+      expect(Chef::ChefFS::PathUtils.realest_path(good_path)).to eq(File.expand_path(good_path))
+    end
+
+    it 'handles paths with .. and .' do
+      expect(Chef::ChefFS::PathUtils.realest_path(good_path+'/../.')).to eq(File.expand_path(parent_path))
+    end
+
+    it 'handles paths with *' do
+      expect(Chef::ChefFS::PathUtils.realest_path(good_path + '/*/foo')).to eq(File.expand_path(good_path + '/*/foo'))
+    end
+
+    it 'handles directories that do not exist' do
+      expect(Chef::ChefFS::PathUtils.realest_path(good_path + '/something/or/other')).to eq(File.expand_path(good_path + '/something/or/other'))
+    end
+
+    it 'handles root correctly' do
+      if Chef::Platform.windows?
+        expect(Chef::ChefFS::PathUtils.realest_path('C:/')).to eq('C:/')
+      else
+        expect(Chef::ChefFS::PathUtils.realest_path('/')).to eq('/')
+      end
+    end
+  end
+
+  context 'invoking descendant_path' do
+    it 'handles paths with various casing on windows' do
+      allow(Chef::ChefFS).to receive(:windows?) { true }
+      expect(Chef::ChefFS::PathUtils.descendant_path('C:/ab/b/c', 'C:/AB/B')).to eq('c')
+      expect(Chef::ChefFS::PathUtils.descendant_path('C:/ab/b/c', 'c:/ab/B')).to eq('c')
+    end
+
+    it 'returns nil if the path does not have the given ancestor' do
+      expect(Chef::ChefFS::PathUtils.descendant_path('/D/E/F', '/A/B/C')).to be_nil
+      expect(Chef::ChefFS::PathUtils.descendant_path('/A/B/D', '/A/B/C')).to be_nil
+    end
+
+    it 'returns blank if the ancestor equals the path' do
+      expect(Chef::ChefFS::PathUtils.descendant_path('/A/B/D', '/A/B/D')).to eq('')
+    end
+  end
+end
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
index fa83177..8fbf568 100644
--- a/spec/unit/client_spec.rb
+++ b/spec/unit/client_spec.rb
@@ -19,6 +19,8 @@
 #
 
 require 'spec_helper'
+require 'spec/support/shared/context/client'
+require 'spec/support/shared/examples/client'
 
 require 'chef/run_context'
 require 'chef/rest'
@@ -28,55 +30,7 @@ class FooError < RuntimeError
 end
 
 describe Chef::Client do
-
-  let(:hostname) { "hostname" }
-  let(:machinename) { "machinename.example.org" }
-  let(:fqdn) { "hostname.example.org" }
-
-  let(:ohai_data) do
-    { :fqdn             => fqdn,
-      :hostname         => hostname,
-      :machinename      => machinename,
-      :platform         => 'example-platform',
-      :platform_version => 'example-platform-1.0',
-      :data             => {}
-    }
-  end
-
-  let(:ohai_system) do
-    ohai_system = double( "Ohai::System",
-                          :all_plugins => true,
-                          :data => ohai_data)
-    allow(ohai_system).to receive(:[]) do |key|
-      ohai_data[key]
-    end
-    ohai_system
-  end
-
-  let(:node) do
-    Chef::Node.new.tap do |n|
-      n.name(fqdn)
-      n.chef_environment("_default")
-    end
-  end
-
-  let(:json_attribs) { nil }
-  let(:client_opts) { {} }
-
-  let(:client) do
-    Chef::Config[:event_loggers] = []
-    Chef::Client.new(json_attribs, client_opts).tap do |c|
-      c.node = node
-    end
-  end
-
-  before do
-    Chef::Log.logger = Logger.new(StringIO.new)
-
-    # Node/Ohai data
-    #Chef::Config[:node_name] = fqdn
-    allow(Ohai::System).to receive(:new).and_return(ohai_system)
-  end
+  include_context "client"
 
   context "when minimal ohai is configured" do
     before do
@@ -88,7 +42,6 @@ describe Chef::Client do
       expect(ohai_system).to receive(:all_plugins).with(expected_filter)
       client.run_ohai
     end
-
   end
 
   describe "authentication protocol selection" do
@@ -117,7 +70,6 @@ describe Chef::Client do
 
   describe "configuring output formatters" do
     context "when no formatter has been configured" do
-
       context "and STDOUT is a TTY" do
         before do
           allow(STDOUT).to receive(:tty?).and_return(true)
@@ -203,135 +155,12 @@ describe Chef::Client do
   end
 
   describe "a full client run" do
-    shared_context "a client run" do
-      let(:http_node_load) { double("Chef::REST (node)") }
-      let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") }
-      let(:http_node_save) { double("Chef::REST (node save)") }
-      let(:runner) { double("Chef::Runner") }
-      let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) }
-
-      let(:api_client_exists?) { false }
-
-      let(:stdout) { StringIO.new }
-      let(:stderr) { StringIO.new }
-
-      let(:enable_fork) { false }
-
-      def stub_for_register
-        # --Client.register
-        #   Make sure Client#register thinks the client key doesn't
-        #   exist, so it tries to register and create one.
-        allow(File).to receive(:exists?).and_call_original
-        expect(File).to receive(:exists?).
-          with(Chef::Config[:client_key]).
-          exactly(:once).
-          and_return(api_client_exists?)
-
-        unless api_client_exists?
-          #   Client.register will register with the validation client name.
-          expect_any_instance_of(Chef::ApiClient::Registration).to receive(:run)
-        end
-      end
-
-      def stub_for_node_load
-        #   Client.register will then turn around create another
-        #   Chef::REST object, this time with the client key it got from the
-        #   previous step.
-        expect(Chef::REST).to receive(:new).
-          with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]).
-          exactly(:once).
-          and_return(http_node_load)
-
-        # --Client#build_node
-        #   looks up the node, which we will return, then later saves it.
-        expect(Chef::Node).to receive(:find_or_create).with(fqdn).and_return(node)
-
-        # --ResourceReporter#node_load_completed
-        #   gets a run id from the server for storing resource history
-        #   (has its own tests, so stubbing it here.)
-        expect_any_instance_of(Chef::ResourceReporter).to receive(:node_load_completed)
-      end
-
-      def stub_for_sync_cookbooks
-        # --Client#setup_run_context
-        # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
-        #
-        expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks)
-        expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
-        expect(http_cookbook_sync).to receive(:post).
-          with("environments/_default/cookbook_versions", {:run_list => []}).
-          and_return({})
-      end
-
-      def stub_for_converge
-        # --Client#converge
-        expect(Chef::Runner).to receive(:new).and_return(runner)
-        expect(runner).to receive(:converge).and_return(true)
-      end
-
-      def stub_for_audit
-        # -- Client#run_audits
-        expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
-        expect(audit_runner).to receive(:run).and_return(true)
-      end
-
-      def stub_for_node_save
-        allow(node).to receive(:data_for_save).and_return(node.for_json)
-
-        # --Client#save_updated_node
-        expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_node_save)
-        expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true)
-      end
-
-      def stub_for_run
-        expect_any_instance_of(Chef::RunLock).to receive(:acquire)
-        expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
-        expect_any_instance_of(Chef::RunLock).to receive(:release)
-
-        # Post conditions: check that node has been filled in correctly
-        expect(client).to receive(:run_started)
-        expect(client).to receive(:run_completed_successfully)
-
-        # --ResourceReporter#run_completed
-        #   updates the server with the resource history
-        #   (has its own tests, so stubbing it here.)
-        expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed)
-        # --AuditReporter#run_completed
-        #   posts the audit data to server.
-        #   (has its own tests, so stubbing it here.)
-        expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed)
-      end
-
-      before do
-        Chef::Config[:client_fork] = enable_fork
-        Chef::Config[:cache_path] = windows? ? 'C:\chef' : '/var/chef'
-        Chef::Config[:why_run] = false
-        Chef::Config[:audit_mode] = :enabled
-
-        stub_const("Chef::Client::STDOUT_FD", stdout)
-        stub_const("Chef::Client::STDERR_FD", stderr)
-
-        stub_for_register
-        stub_for_node_load
-        stub_for_sync_cookbooks
-        stub_for_converge
-        stub_for_audit
-        stub_for_node_save
-        stub_for_run
-      end
-    end
-
     shared_examples_for "a successful client run" do
       include_context "a client run"
+      include_context "converge completed"
+      include_context "audit phase completed"
 
-      it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do
-        # This is what we're testing.
-        client.run
-
-        # fork is stubbed, so we can see the outcome of the run
-        expect(node.automatic_attrs[:platform]).to eq("example-platform")
-        expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0")
-      end
+      include_examples "a completed run"
     end
 
     describe "when running chef-client without fork" do
@@ -339,24 +168,19 @@ describe Chef::Client do
     end
 
     describe "when the client key already exists" do
-      let(:api_client_exists?) { true }
-      include_examples "a successful client run"
+      include_examples "a successful client run" do
+        let(:api_client_exists?) { true }
+      end
     end
 
-    describe "when an override run list is given" do
-      let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} }
-
-      it "should permit spaces in overriding run list" do
+    context "when an override run list is given" do
+      it "permits spaces in overriding run list" do
         Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
       end
 
-      describe "when running the client" do
+      describe "calling run" do
         include_examples "a successful client run" do
-
-          before do
-            # Client will try to compile and run override_recipe
-            expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile)
-          end
+          let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} }
 
           def stub_for_sync_cookbooks
             # --Client#setup_run_context
@@ -373,13 +197,22 @@ describe Chef::Client do
             # Expect NO node save
             expect(node).not_to receive(:save)
           end
+
+          before do
+            # Client will try to compile and run override_recipe
+            expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile)
+          end
         end
       end
     end
 
     describe "when a permanent run list is passed as an option" do
-      include_examples "a successful client run" do
+      it "sets the new run list on the node" do
+        client.run
+        expect(node.run_list).to eq(Chef::RunList.new(new_runlist))
+      end
 
+      include_examples "a successful client run" do
         let(:new_runlist) { "recipe[new_run_list_recipe]" }
         let(:client_opts) { {:runlist => new_runlist} }
 
@@ -399,214 +232,62 @@ describe Chef::Client do
           # do not create a fixture for this.
           expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile)
         end
-
-        it "sets the new run list on the node" do
-          client.run
-          expect(node.run_list).to eq(Chef::RunList.new(new_runlist))
-        end
       end
     end
 
-    describe "when converge fails" do
-      include_context "a client run" do
-        let(:e) { Exception.new }
-        def stub_for_converge
-          expect(Chef::Runner).to receive(:new).and_return(runner)
-          expect(runner).to receive(:converge).and_raise(e)
-          expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
-        end
-
-        def stub_for_node_save
-          expect(client).to_not receive(:save_updated_node)
-        end
-
-        def stub_for_run
-          expect_any_instance_of(Chef::RunLock).to receive(:acquire)
-          expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
-          expect_any_instance_of(Chef::RunLock).to receive(:release)
-
-          # Post conditions: check that node has been filled in correctly
-          expect(client).to receive(:run_started)
-          expect(client).to receive(:run_failed)
-
-          expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
-          expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
-        end
-      end
-
-      it "runs the audits and raises the error" do
-        expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
-          expect(error.wrapped_errors.size).to eq(1)
-          expect(error.wrapped_errors[0]).to eq(e)
-        end
-      end
-    end
-
-    describe "when the audit phase fails" do
-      context "with an exception" do
-        context "when audit mode is enabled" do
-          include_context "a client run" do
-            let(:e) { Exception.new }
-            def stub_for_audit
-              expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
-              expect(audit_runner).to receive(:run).and_raise(e)
-              expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
-            end
-
-            def stub_for_run
-              expect_any_instance_of(Chef::RunLock).to receive(:acquire)
-              expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
-              expect_any_instance_of(Chef::RunLock).to receive(:release)
-
-              # Post conditions: check that node has been filled in correctly
-              expect(client).to receive(:run_started)
-              expect(client).to receive(:run_failed)
-
-              expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
-              expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
-            end
-          end
-
-          it "should save the node after converge and raise exception" do
-            expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
-              expect(error.wrapped_errors.size).to eq(1)
-              expect(error.wrapped_errors[0]).to eq(e)
-            end
-          end
-        end
-
-        context "when audit mode is disabled" do
-          include_context "a client run" do
-            before do
-              Chef::Config[:audit_mode] = :disabled
-            end
-
-            let(:e) { FooError.new }
-
-            def stub_for_audit
-              expect(Chef::Audit::Runner).to_not receive(:new)
-            end
-
-            def stub_for_converge
-              expect(Chef::Runner).to receive(:new).and_return(runner)
-              expect(runner).to receive(:converge).and_raise(e)
-              expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(FooError)
-            end
-
-            def stub_for_node_save
-              expect(client).to_not receive(:save_updated_node)
-            end
-
-            def stub_for_run
-              expect_any_instance_of(Chef::RunLock).to receive(:acquire)
-              expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
-              expect_any_instance_of(Chef::RunLock).to receive(:release)
-
-
-              # Post conditions: check that node has been filled in correctly
-              expect(client).to receive(:run_started)
-              expect(client).to receive(:run_failed)
-
-              expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
-
-            end
-
-            it "re-raises an unwrapped exception" do
-              expect { client.run }.to raise_error(FooError)
-            end
+    describe "when converge completes successfully" do
+      include_context "a client run"
+      include_context "converge completed"
+      context 'when audit mode is enabled' do
+        describe "when audit phase errors" do
+          include_context "audit phase failed with error"
+          include_examples "a completed run with audit failure" do
+            let(:run_errors) { [audit_error] }
           end
         end
 
-
-      end
-
-      context "with failed audits" do
-        include_context "a client run" do
-          let(:audit_runner) do
-            instance_double("Chef::Audit::Runner", :run => true, :failed? => true, :num_failed => 1, :num_total => 1)
-          end
-
-          def stub_for_audit
-            expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
-            expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
-          end
-
-          def stub_for_run
-            expect_any_instance_of(Chef::RunLock).to receive(:acquire)
-            expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
-            expect_any_instance_of(Chef::RunLock).to receive(:release)
-
-            # Post conditions: check that node has been filled in correctly
-            expect(client).to receive(:run_started)
-            expect(client).to receive(:run_failed)
-
-            expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
-            expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
-          end
+        describe "when audit phase completed" do
+          include_context "audit phase completed"
+          include_examples "a completed run"
         end
 
-        it "should save the node after converge and raise exception" do
-          expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
-            expect(error.wrapped_errors.size).to eq(1)
-            expect(error.wrapped_errors[0]).to be_instance_of(Chef::Exceptions::AuditsFailed)
+        describe "when audit phase completed with failed controls" do
+          include_context "audit phase completed with failed controls"
+          include_examples "a completed run with audit failure" do
+            let(:run_errors) { [audit_error] }
           end
         end
       end
     end
 
-    describe "when why_run mode is enabled" do
-      include_context "a client run" do
-
-        before do
-          Chef::Config[:why_run] = true
-        end
-
-        def stub_for_audit
-          expect(Chef::Audit::Runner).to_not receive(:new)
-        end
-
-        def stub_for_node_save
-          # This is how we should be mocking external calls - not letting it fall all the way through to the
-          # REST call
-          expect(node).to receive(:save)
-        end
-
-        it "runs successfully without enabling the audit runner" do
-          client.run
+    describe "when converge errors" do
+      include_context "a client run"
+      include_context "converge failed"
 
-          # fork is stubbed, so we can see the outcome of the run
-          expect(node.automatic_attrs[:platform]).to eq("example-platform")
-          expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0")
+      describe "when audit phase errors" do
+        include_context "audit phase failed with error"
+        include_examples "a failed run" do
+          let(:run_errors) { [converge_error, audit_error] }
         end
       end
-    end
-
-    describe "when audits are disabled" do
-      include_context "a client run" do
-
-        before do
-          Chef::Config[:audit_mode] = :disabled
-        end
 
-        def stub_for_audit
-          expect(Chef::Audit::Runner).to_not receive(:new)
+      describe "when audit phase completed" do
+        include_context "audit phase completed"
+        include_examples "a failed run" do
+          let(:run_errors) { [converge_error] }
         end
+      end
 
-        it "runs successfully without enabling the audit runner" do
-          client.run
-
-          # fork is stubbed, so we can see the outcome of the run
-          expect(node.automatic_attrs[:platform]).to eq("example-platform")
-          expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0")
+      describe "when audit phase completed with failed controls" do
+        include_context "audit phase completed with failed controls"
+        include_examples "a failed run" do
+          let(:run_errors) { [converge_error, audit_error] }
         end
       end
     end
-
   end
 
-
   describe "when handling run failures" do
-
     it "should remove the run_lock on failure of #load_node" do
       @run_lock = double("Chef::RunLock", :acquire => true)
       allow(Chef::RunLock).to receive(:new).and_return(@run_lock)
@@ -680,8 +361,11 @@ describe Chef::Client do
       # check pre-conditions.
       expect(node[:roles]).to be_nil
       expect(node[:recipes]).to be_nil
+      expect(node[:expanded_run_list]).to be_nil
 
       allow(client.policy_builder).to receive(:node).and_return(node)
+      client.policy_builder.select_implementation(node)
+      allow(client.policy_builder.implementation).to receive(:node).and_return(node)
 
       # chefspec and possibly others use the return value of this method
       expect(client.build_node).to eq(node)
@@ -691,8 +375,12 @@ describe Chef::Client do
       expect(node[:roles].length).to eq(1)
       expect(node[:roles]).to include("role_containing_cookbook1")
       expect(node[:recipes]).not_to be_nil
-      expect(node[:recipes].length).to eq(1)
+      expect(node[:recipes].length).to eq(2)
       expect(node[:recipes]).to include("cookbook1")
+      expect(node[:recipes]).to include("cookbook1::default")
+      expect(node[:expanded_run_list]).not_to be_nil
+      expect(node[:expanded_run_list].length).to eq(1)
+      expect(node[:expanded_run_list]).to include("cookbook1::default")
     end
 
     it "should set the environment from the specified configuration value" do
@@ -706,6 +394,8 @@ describe Chef::Client do
       expect(mock_chef_rest).to receive(:get_rest).with("environments/A").and_return(test_env)
       expect(Chef::REST).to receive(:new).and_return(mock_chef_rest)
       allow(client.policy_builder).to receive(:node).and_return(node)
+      client.policy_builder.select_implementation(node)
+      allow(client.policy_builder.implementation).to receive(:node).and_return(node)
       expect(client.build_node).to eq(node)
 
       expect(node.chef_environment).to eq("A")
@@ -715,7 +405,7 @@ describe Chef::Client do
   describe "windows_admin_check" do
     context "platform is not windows" do
       before do
-        allow(Chef::Platform).to receive(:windows?).and_return(false)
+        allow(ChefConfig).to receive(:windows?).and_return(false)
       end
 
       it "shouldn't be called" do
@@ -726,7 +416,7 @@ describe Chef::Client do
 
     context "platform is windows" do
       before do
-        allow(Chef::Platform).to receive(:windows?).and_return(true)
+        allow(ChefConfig).to receive(:windows?).and_return(true)
       end
 
       it "should be called" do
@@ -775,6 +465,7 @@ describe Chef::Client do
       Chef::Config[:solo] = true
       Chef::Config[:cookbook_path] = ["/path/to/invalid/cookbook_path"]
     end
+
     context "when any directory of cookbook_path contains no cookbook" do
       it "raises CookbookNotFound error" do
         expect do
@@ -819,4 +510,35 @@ describe Chef::Client do
     end
 
   end
+
+  describe "always attempt to run handlers" do
+    subject { client }
+    before do
+      # fail on the first thing in begin block
+      allow_any_instance_of(Chef::RunLock).to receive(:save_pid).and_raise(NoMethodError)
+    end
+
+    context 'when audit mode is enabled' do
+      before do
+        Chef::Config[:audit_mode] = :enabled
+      end
+      it "should run exception handlers on early fail" do
+        expect(subject).to receive(:run_failed)
+        expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+          expect(error.wrapped_errors.size).to eq 1
+          expect(error.wrapped_errors).to include(NoMethodError)
+        end
+      end
+    end
+
+    context 'when audit mode is disabled' do
+      before do
+        Chef::Config[:audit_mode] = :disabled
+      end
+      it "should run exception handlers on early fail" do
+        expect(subject).to receive(:run_failed)
+        expect { subject.run }.to raise_error(NoMethodError)
+      end
+    end
+  end
 end
diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb
index 6ea6724..8d155c6 100644
--- a/spec/unit/config_spec.rb
+++ b/spec/unit/config_spec.rb
@@ -1,544 +1,31 @@
-#
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Kyle Goodwin (<kgoodwin at primerevenue.com>)
-# Copyright:: Copyright (c) 2008 Opscode, 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 'spec_helper'
-require 'chef/exceptions'
-require 'chef/util/path_helper'
 
-describe Chef::Config do
-  describe "config attribute writer: chef_server_url" do
-    before do
-      Chef::Config.chef_server_url = "https://junglist.gen.nz"
-    end
-
-    it "sets the server url" do
-      expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz")
-    end
-
-    context "when the url has a leading space" do
-      before do
-        Chef::Config.chef_server_url = " https://junglist.gen.nz"
-      end
-
-      it "strips the space from the url when setting" do
-        expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz")
-      end
-
-    end
+require 'chef/config'
 
-    context "when the url is a frozen string" do
-      before do
-        Chef::Config.chef_server_url = " https://junglist.gen.nz".freeze
-      end
+RSpec.describe Chef::Config do
 
-      it "strips the space from the url when setting without raising an error" do
-        expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz")
-      end
+  shared_examples_for "deprecated by ohai but not deprecated" do
+    it "does not emit a deprecation warning when set" do
+      expect(Chef::Log).to_not receive(:warn).
+        with(/Ohai::Config\[:#{option}\] is deprecated/)
+      Chef::Config[option] = value
+      expect(Chef::Config[option]).to eq(value)
     end
-
   end
 
-  describe "when configuring formatters" do
-      # if TTY and not(force-logger)
-      #   formatter = configured formatter or default formatter
-      #   formatter goes to STDOUT/ERR
-      #   if log file is writeable
-      #     log level is configured level or info
-      #     log location is file
-      #   else
-      #     log level is warn
-      #     log location is STDERR
-      #    end
-      # elsif not(TTY) and force formatter
-      #   formatter = configured formatter or default formatter
-      #   if log_location specified
-      #     formatter goes to log_location
-      #   else
-      #     formatter goes to STDOUT/ERR
-      #   end
-      # else
-      #   formatter = "null"
-      #   log_location = configured-value or defualt
-      #   log_level = info or defualt
-      # end
-      #
-    it "has an empty list of formatters by default" do
-      expect(Chef::Config.formatters).to eq([])
-    end
-
-    it "configures a formatter with a short name" do
-      Chef::Config.add_formatter(:doc)
-      expect(Chef::Config.formatters).to eq([[:doc, nil]])
+  describe ":log_level" do
+    include_examples "deprecated by ohai but not deprecated" do
+      let(:option) { :log_level }
+      let(:value) { :debug }
     end
-
-    it "configures a formatter with a file output" do
-      Chef::Config.add_formatter(:doc, "/var/log/formatter.log")
-      expect(Chef::Config.formatters).to eq([[:doc, "/var/log/formatter.log"]])
-    end
-
   end
 
-  describe "class method: manage_secret_key" do
-    before do
-      allow(Chef::FileCache).to receive(:load).and_return(true)
-      allow(Chef::FileCache).to receive(:has_key?).with("chef_server_cookie_id").and_return(false)
+  describe ":log_location" do
+    include_examples "deprecated by ohai but not deprecated" do
+      let(:option) { :log_location }
+      let(:value) { "path/to/log" }
     end
-
-    it "should generate and store a chef server cookie id" do
-      expect(Chef::FileCache).to receive(:store).with("chef_server_cookie_id", /\w{40}/).and_return(true)
-      Chef::Config.manage_secret_key
-    end
-
-    describe "when the filecache has a chef server cookie id key" do
-      before do
-        allow(Chef::FileCache).to receive(:has_key?).with("chef_server_cookie_id").and_return(true)
-      end
-
-      it "should not generate and store a chef server cookie id" do
-        expect(Chef::FileCache).not_to receive(:store).with("chef_server_cookie_id", /\w{40}/)
-        Chef::Config.manage_secret_key
-      end
-    end
-
   end
 
-  [ false, true ].each do |is_windows|
-
-    context "On #{is_windows ? 'Windows' : 'Unix'}" do
-      def to_platform(*args)
-        Chef::Config.platform_specific_path(*args)
-      end
-
-      before :each do
-        allow(Chef::Platform).to receive(:windows?).and_return(is_windows)
-      end
-
-      describe "class method: platform_specific_path" do
-        if is_windows
-          it "should return a windows path on windows systems" do
-            path = "/etc/chef/cookbooks"
-            allow(Chef::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' })
-            # match on a regex that looks for the base path with an optional
-            # system drive at the beginning (c:)
-            # system drive is not hardcoded b/c it can change and b/c it is not present on linux systems
-            expect(Chef::Config.platform_specific_path(path)).to eq("C:\\chef\\cookbooks")
-          end
-        else
-          it "should return given path on non-windows systems" do
-            path = "/etc/chef/cookbooks"
-            expect(Chef::Config.platform_specific_path(path)).to eq("/etc/chef/cookbooks")
-          end
-        end
-      end
-
-      describe "default values" do
-        let :primary_cache_path do
-          if is_windows
-            "#{Chef::Config.env['SYSTEMDRIVE']}\\chef"
-          else
-            "/var/chef"
-          end
-        end
-
-        let :secondary_cache_path do
-          if is_windows
-            "#{Chef::Config[:user_home]}\\.chef"
-          else
-            "#{Chef::Config[:user_home]}/.chef"
-          end
-        end
-
-        before do
-          if is_windows
-            allow(Chef::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' })
-            Chef::Config[:user_home] = 'C:\Users\charlie'
-          else
-            Chef::Config[:user_home] = '/Users/charlie'
-          end
-
-          allow(Chef::Config).to receive(:path_accessible?).and_return(false)
-        end
-
-        describe "Chef::Config[:cache_path]" do
-          context "when /var/chef exists and is accessible" do
-            it "defaults to /var/chef" do
-              allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var/chef")).and_return(true)
-              expect(Chef::Config[:cache_path]).to eq(primary_cache_path)
-            end
-          end
-
-          context "when /var/chef does not exist and /var is accessible" do
-            it "defaults to /var/chef" do
-              allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false)
-              allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(true)
-              expect(Chef::Config[:cache_path]).to eq(primary_cache_path)
-            end
-          end
-
-          context "when /var/chef does not exist and /var is not accessible" do
-            it "defaults to $HOME/.chef" do
-              allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false)
-              allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(false)
-              expect(Chef::Config[:cache_path]).to eq(secondary_cache_path)
-            end
-          end
-
-          context "when /var/chef exists and is not accessible" do
-            it "defaults to $HOME/.chef" do
-              allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(true)
-              allow(File).to receive(:readable?).with(to_platform("/var/chef")).and_return(true)
-              allow(File).to receive(:writable?).with(to_platform("/var/chef")).and_return(false)
-
-              expect(Chef::Config[:cache_path]).to eq(secondary_cache_path)
-            end
-          end
-
-          context "when chef is running in local mode" do
-            before do
-              Chef::Config.local_mode = true
-            end
-
-            context "and config_dir is /a/b/c" do
-              before do
-                Chef::Config.config_dir to_platform('/a/b/c')
-              end
-
-              it "cache_path is /a/b/c/local-mode-cache" do
-                expect(Chef::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache'))
-              end
-            end
-
-            context "and config_dir is /a/b/c/" do
-              before do
-                Chef::Config.config_dir to_platform('/a/b/c/')
-              end
-
-              it "cache_path is /a/b/c/local-mode-cache" do
-                expect(Chef::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache'))
-              end
-            end
-          end
-        end
-
-        it "Chef::Config[:file_backup_path] defaults to /var/chef/backup" do
-          allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path)
-          backup_path = is_windows ? "#{primary_cache_path}\\backup" : "#{primary_cache_path}/backup"
-          expect(Chef::Config[:file_backup_path]).to eq(backup_path)
-        end
-
-        it "Chef::Config[:ssl_verify_mode] defaults to :verify_peer" do
-          expect(Chef::Config[:ssl_verify_mode]).to eq(:verify_peer)
-        end
-
-        it "Chef::Config[:ssl_ca_path] defaults to nil" do
-          expect(Chef::Config[:ssl_ca_path]).to be_nil
-        end
-
-        # TODO can this be removed?
-        if !is_windows
-          it "Chef::Config[:ssl_ca_file] defaults to nil" do
-            expect(Chef::Config[:ssl_ca_file]).to be_nil
-          end
-        end
-
-        it "Chef::Config[:data_bag_path] defaults to /var/chef/data_bags" do
-          allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path)
-          data_bag_path = is_windows ? "#{primary_cache_path}\\data_bags" : "#{primary_cache_path}/data_bags"
-          expect(Chef::Config[:data_bag_path]).to eq(data_bag_path)
-        end
-
-        it "Chef::Config[:environment_path] defaults to /var/chef/environments" do
-          allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path)
-          environment_path = is_windows ? "#{primary_cache_path}\\environments" : "#{primary_cache_path}/environments"
-          expect(Chef::Config[:environment_path]).to eq(environment_path)
-        end
-
-        describe "setting the config dir" do
-
-          context "when the config file is /etc/chef/client.rb" do
-
-            before do
-              Chef::Config.config_file = to_platform("/etc/chef/client.rb")
-            end
-
-            it "config_dir is /etc/chef" do
-              expect(Chef::Config.config_dir).to eq(to_platform("/etc/chef"))
-            end
-
-            context "and chef is running in local mode" do
-              before do
-                Chef::Config.local_mode = true
-              end
-
-              it "config_dir is /etc/chef" do
-                expect(Chef::Config.config_dir).to eq(to_platform("/etc/chef"))
-              end
-            end
-
-            context "when config_dir is set to /other/config/dir/" do
-              before do
-                Chef::Config.config_dir = to_platform("/other/config/dir/")
-              end
-
-              it "yields the explicit value" do
-                expect(Chef::Config.config_dir).to eq(to_platform("/other/config/dir/"))
-              end
-            end
-
-          end
-
-          context "when the user's home dir is /home/charlie/" do
-            before do
-              Chef::Config.user_home = to_platform("/home/charlie")
-            end
-
-            it "config_dir is /home/charlie/.chef/" do
-              expect(Chef::Config.config_dir).to eq(Chef::Util::PathHelper.join(to_platform("/home/charlie/.chef"), ''))
-            end
-
-            context "and chef is running in local mode" do
-              before do
-                Chef::Config.local_mode = true
-              end
-
-              it "config_dir is /home/charlie/.chef/" do
-                expect(Chef::Config.config_dir).to eq(Chef::Util::PathHelper.join(to_platform("/home/charlie/.chef"), ''))
-              end
-            end
-          end
-
-        end
-
-        if is_windows
-          describe "finding the windows embedded dir" do
-            let(:default_config_location) { "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" }
-            let(:alternate_install_location) { "c:/my/alternate/install/place/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" }
-            let(:non_omnibus_location) { "c:/my/dev/stuff/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" }
-
-            let(:default_ca_file) { "c:/opscode/chef/embedded/ssl/certs/cacert.pem" }
-
-            it "finds the embedded dir in the default location" do
-              allow(Chef::Config).to receive(:_this_file).and_return(default_config_location)
-              expect(Chef::Config.embedded_dir).to eq("c:/opscode/chef/embedded")
-            end
-
-            it "finds the embedded dir in a custom install location" do
-              allow(Chef::Config).to receive(:_this_file).and_return(alternate_install_location)
-              expect(Chef::Config.embedded_dir).to eq("c:/my/alternate/install/place/chef/embedded")
-            end
-
-            it "doesn't error when not in an omnibus install" do
-              allow(Chef::Config).to receive(:_this_file).and_return(non_omnibus_location)
-              expect(Chef::Config.embedded_dir).to be_nil
-            end
-
-            it "sets the ssl_ca_cert path if the cert file is available" do
-              allow(Chef::Config).to receive(:_this_file).and_return(default_config_location)
-              allow(File).to receive(:exist?).with(default_ca_file).and_return(true)
-              expect(Chef::Config.ssl_ca_file).to eq(default_ca_file)
-            end
-          end
-        end
-      end
-
-      describe "Chef::Config[:user_home]" do
-        it "should set when HOME is provided" do
-          expected = to_platform("/home/kitten")
-          allow(Chef::Util::PathHelper).to receive(:home).and_return(expected)
-          expect(Chef::Config[:user_home]).to eq(expected)
-        end
-
-        it "falls back to the current working directory when HOME and USERPROFILE is not set" do
-          allow(Chef::Util::PathHelper).to receive(:home).and_return(nil)
-          expect(Chef::Config[:user_home]).to eq(Dir.pwd)
-        end
-      end
-
-      describe "Chef::Config[:encrypted_data_bag_secret]" do
-        let(:db_secret_default_path){ to_platform("/etc/chef/encrypted_data_bag_secret") }
-
-        before do
-          allow(File).to receive(:exist?).with(db_secret_default_path).and_return(secret_exists)
-        end
-
-        context "/etc/chef/encrypted_data_bag_secret exists" do
-          let(:secret_exists) { true }
-          it "sets the value to /etc/chef/encrypted_data_bag_secret" do
-            expect(Chef::Config[:encrypted_data_bag_secret]).to eq db_secret_default_path
-          end
-        end
-
-        context "/etc/chef/encrypted_data_bag_secret does not exist" do
-          let(:secret_exists) { false }
-          it "sets the value to nil" do
-            expect(Chef::Config[:encrypted_data_bag_secret]).to be_nil
-          end
-        end
-      end
-
-      describe "Chef::Config[:event_handlers]" do
-        it "sets a event_handlers to an empty array by default" do
-          expect(Chef::Config[:event_handlers]).to eq([])
-        end
-        it "should be able to add custom handlers" do
-          o = Object.new
-          Chef::Config[:event_handlers] << o
-          expect(Chef::Config[:event_handlers]).to be_include(o)
-        end
-      end
-
-      describe "Chef::Config[:user_valid_regex]" do
-        context "on a platform that is not Windows" do
-          it "allows one letter usernames" do
-            any_match = Chef::Config[:user_valid_regex].any? { |regex| regex.match('a') }
-            expect(any_match).to be_truthy
-          end
-        end
-      end
-
-      describe "Chef::Config[:internal_locale]" do
-        let(:shell_out) do
-          double("Chef::Mixin::ShellOut double", :exitstatus => 0, :stdout => locales)
-        end
-
-        let(:locales) { locale_array.join("\n") }
-
-        before do
-          allow(Chef::Config).to receive(:shell_out_with_systems_locale!).with("locale -a").and_return(shell_out)
-        end
-
-        shared_examples_for "a suitable locale" do
-          it "returns an English UTF-8 locale" do
-            expect(Chef::Log).to_not receive(:warn).with(/Please install an English UTF-8 locale for Chef to use/)
-            expect(Chef::Log).to_not receive(:debug).with(/Defaulting to locale en_US.UTF-8 on Windows/)
-            expect(Chef::Log).to_not receive(:debug).with(/No usable locale -a command found/)
-            expect(Chef::Config.guess_internal_locale).to eq expected_locale
-          end
-        end
-
-        context "when the result includes 'C.UTF-8'" do
-          include_examples "a suitable locale" do
-            let(:locale_array) { [expected_locale, "en_US.UTF-8"] }
-            let(:expected_locale) { "C.UTF-8" }
-          end
-        end
-
-        context "when the result includes 'en_US.UTF-8'" do
-          include_examples "a suitable locale" do
-            let(:locale_array) { ["en_CA.UTF-8", expected_locale, "en_NZ.UTF-8"] }
-            let(:expected_locale) { "en_US.UTF-8" }
-          end
-        end
-
-        context "when the result includes 'en_US.utf8'" do
-          include_examples "a suitable locale" do
-            let(:locale_array) { ["en_CA.utf8", "en_US.utf8", "en_NZ.utf8"] }
-            let(:expected_locale) { "en_US.UTF-8" }
-          end
-        end
-
-        context "when the result includes 'en.UTF-8'" do
-          include_examples "a suitable locale" do
-            let(:locale_array) { ["en.ISO8859-1", expected_locale] }
-            let(:expected_locale) { "en.UTF-8" }
-          end
-        end
-
-        context "when the result includes 'en_*.UTF-8'" do
-          include_examples "a suitable locale" do
-            let(:locale_array) { [expected_locale, "en_CA.UTF-8", "en_GB.UTF-8"] }
-            let(:expected_locale) { "en_AU.UTF-8" }
-          end
-        end
-
-        context "when the result includes 'en_*.utf8'" do
-          include_examples "a suitable locale" do
-            let(:locale_array) { ["en_AU.utf8", "en_CA.utf8", "en_GB.utf8"] }
-            let(:expected_locale) { "en_AU.UTF-8" }
-          end
-        end
-
-        context "when the result does not include 'en_*.UTF-8'" do
-          let(:locale_array) { ["af_ZA", "af_ZA.ISO8859-1", "af_ZA.ISO8859-15", "af_ZA.UTF-8"] }
-
-          it "should fall back to C locale" do
-            expect(Chef::Log).to receive(:warn).with("Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support.")
-            expect(Chef::Config.guess_internal_locale).to eq 'C'
-          end
-        end
-
-        context "on error" do
-          let(:locale_array) { [] }
-
-          before do
-            allow(Chef::Config).to receive(:shell_out_with_systems_locale!).and_raise("THIS IS AN ERROR")
-          end
-
-          it "should default to 'en_US.UTF-8'" do
-            if is_windows
-              expect(Chef::Log).to receive(:debug).with("Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else.")
-            else
-              expect(Chef::Log).to receive(:debug).with("No usable locale -a command found, assuming you have en_US.UTF-8 installed.")
-            end
-            expect(Chef::Config.guess_internal_locale).to eq "en_US.UTF-8"
-          end
-        end
-      end
-    end
-  end
-
-  describe "Treating deprecation warnings as errors" do
-
-    context "when using our default RSpec configuration" do
-
-      it "defaults to treating deprecation warnings as errors" do
-        expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(true)
-      end
-
-      it "sets CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS environment variable" do
-        expect(ENV['CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS']).to eq("1")
-      end
-
-      it "treats deprecation warnings as errors in child processes when testing" do
-        # Doing a full integration test where we launch a child process is slow
-        # and liable to break for weird reasons (bundler env stuff, etc.), so
-        # we're just checking that the presence of the environment variable
-        # causes treat_deprecation_warnings_as_errors to be set to true after a
-        # config reset.
-        Chef::Config.reset
-        expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(true)
-      end
-
-    end
-
-    context "outside of our test environment" do
-
-      before do
-        ENV.delete('CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS')
-        Chef::Config.reset
-      end
-
-      it "defaults to NOT treating deprecation warnings as errors" do
-        expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(false)
-      end
-    end
-
-
-  end
 end
diff --git a/spec/unit/cookbook/cookbook_version_loader_spec.rb b/spec/unit/cookbook/cookbook_version_loader_spec.rb
index 2c4ad11..23ffc21 100644
--- a/spec/unit/cookbook/cookbook_version_loader_spec.rb
+++ b/spec/unit/cookbook/cookbook_version_loader_spec.rb
@@ -20,7 +20,7 @@ require 'spec_helper'
 
 describe Chef::Cookbook::CookbookVersionLoader do
   before do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
   end
 
   describe "loading a cookbook" do
diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb
index 760ae5d..f8e96ad 100644
--- a/spec/unit/cookbook/metadata_spec.rb
+++ b/spec/unit/cookbook/metadata_spec.rb
@@ -1,7 +1,7 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Seth Falcon (<seth at opscode.com>)
-# Copyright:: Copyright 2008-2010 Opscode, Inc.
+# Copyright:: Copyright 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,7 +30,7 @@ describe Chef::Cookbook::Metadata do
                   :maintainer_email, :license, :platforms, :dependencies,
                   :recommendations, :suggestions, :conflicting, :providing,
                   :replacing, :attributes, :groupings, :recipes, :version,
-                  :source_url, :issues_url ]
+                  :source_url, :issues_url, :privacy, :ohai_versions, :chef_versions ]
     end
 
     it "does not depend on object identity for equality" do
@@ -148,6 +148,10 @@ describe Chef::Cookbook::Metadata do
     it "has an empty issues_url string" do
       expect(metadata.issues_url).to eq('')
     end
+
+    it "is not private" do
+      expect(metadata.privacy).to eq(false)
+    end
   end
 
   describe "validation" do
@@ -198,7 +202,8 @@ describe Chef::Cookbook::Metadata do
       :long_description => "Much Longer\nSeriously",
       :version => "0.6.0",
       :source_url => "http://example.com",
-      :issues_url => "http://example.com/issues"
+      :issues_url => "http://example.com/issues",
+      :privacy => true
     }
     params.sort { |a,b| a.to_s <=> b.to_s }.each do |field, field_value|
       describe field do
@@ -304,6 +309,125 @@ describe Chef::Cookbook::Metadata do
         end
       end
     end
+
+    it "strips out self-dependencies", :chef_lt_13_only do
+      metadata.name('foo')
+      expect(Chef::Log).to receive(:warn).with(
+        "Ignoring self-dependency in cookbook foo, please remove it (in the future this will be fatal)."
+      )
+      metadata.depends('foo')
+      expect(metadata.dependencies).to eql({})
+    end
+
+    it "errors on self-dependencies", :chef_gte_13_only do
+      metadata.name('foo')
+      expect { metadata.depends('foo') }.to raise_error
+      # FIXME: add the error type
+    end
+  end
+
+  describe "chef_version" do
+    def expect_chef_version_works(*args)
+      ret = []
+      args.each do |arg|
+        metadata.send(:chef_version, *arg)
+        ret << Gem::Dependency.new("chef", *arg)
+      end
+      expect(metadata.send(:chef_versions)).to eql(ret)
+    end
+
+    it "should work with a single simple constraint" do
+      expect_chef_version_works(["~> 12"])
+    end
+
+    it "should work with a single complex constraint" do
+      expect_chef_version_works([">= 12.0.1", "< 12.5.1"])
+    end
+
+    it "should work with multiple simple constraints" do
+      expect_chef_version_works(["~> 12.5.1"],["~> 11.18.10"])
+    end
+
+    it "should work with multiple complex constraints" do
+      expect_chef_version_works([">= 11.14.2", "< 11.18.10"],[">= 12.2.1", "< 12.5.1"])
+    end
+
+    it "should fail validation on a simple pessimistic constraint" do
+      expect_chef_version_works(["~> 999.0"])
+      expect { metadata.validate_chef_version! }.to raise_error(Chef::Exceptions::CookbookChefVersionMismatch)
+    end
+
+    it "should fail validation when that valid chef versions are too big" do
+      expect_chef_version_works([">= 999.0", "< 999.9"])
+      expect { metadata.validate_chef_version! }.to raise_error(Chef::Exceptions::CookbookChefVersionMismatch)
+    end
+
+    it "should fail validation when that valid chef versions are too small" do
+      expect_chef_version_works([">= 0.0.1", "< 0.0.9"])
+      expect { metadata.validate_chef_version! }.to raise_error(Chef::Exceptions::CookbookChefVersionMismatch)
+    end
+
+    it "should fail validation when all ranges fail" do
+      expect_chef_version_works([">= 999.0", "< 999.9"],[">= 0.0.1", "< 0.0.9"])
+      expect { metadata.validate_chef_version! }.to raise_error(Chef::Exceptions::CookbookChefVersionMismatch)
+    end
+
+    it "should pass validation when one constraint passes" do
+      expect_chef_version_works([">= 999.0", "< 999.9"],["= #{Chef::VERSION}"])
+      expect { metadata.validate_chef_version! }.not_to raise_error
+    end
+  end
+
+  describe "ohai_version" do
+    def expect_ohai_version_works(*args)
+      ret = []
+      args.each do |arg|
+        metadata.send(:ohai_version, *arg)
+        ret << Gem::Dependency.new("ohai", *arg)
+      end
+      expect(metadata.send(:ohai_versions)).to eql(ret)
+    end
+
+    it "should work with a single simple constraint" do
+      expect_ohai_version_works(["~> 12"])
+    end
+
+    it "should work with a single complex constraint" do
+      expect_ohai_version_works([">= 12.0.1", "< 12.5.1"])
+    end
+
+    it "should work with multiple simple constraints" do
+      expect_ohai_version_works(["~> 12.5.1"],["~> 11.18.10"])
+    end
+
+    it "should work with multiple complex constraints" do
+      expect_ohai_version_works([">= 11.14.2", "< 11.18.10"],[">= 12.2.1", "< 12.5.1"])
+    end
+
+    it "should fail validation on a simple pessimistic constraint" do
+      expect_ohai_version_works(["~> 999.0"])
+      expect { metadata.validate_ohai_version! }.to raise_error(Chef::Exceptions::CookbookOhaiVersionMismatch)
+    end
+
+    it "should fail validation when that valid chef versions are too big" do
+      expect_ohai_version_works([">= 999.0", "< 999.9"])
+      expect { metadata.validate_ohai_version! }.to raise_error(Chef::Exceptions::CookbookOhaiVersionMismatch)
+    end
+
+    it "should fail validation when that valid chef versions are too small" do
+      expect_ohai_version_works([">= 0.0.1", "< 0.0.9"])
+      expect { metadata.validate_ohai_version! }.to raise_error(Chef::Exceptions::CookbookOhaiVersionMismatch)
+    end
+
+    it "should fail validation when all ranges fail" do
+      expect_ohai_version_works([">= 999.0", "< 999.9"],[">= 0.0.1", "< 0.0.9"])
+      expect { metadata.validate_ohai_version! }.to raise_error(Chef::Exceptions::CookbookOhaiVersionMismatch)
+    end
+
+    it "should pass validation when one constraint passes" do
+      expect_ohai_version_works([">= 999.0", "< 999.9"],["= #{Ohai::VERSION}"])
+      expect { metadata.validate_ohai_version! }.not_to raise_error
+    end
   end
 
   describe "attribute groupings" do
@@ -345,7 +469,8 @@ describe Chef::Cookbook::Metadata do
         "recipes" => [ "mysql::server", "mysql::master" ],
         "default" => [ ],
         "source_url" => "http://example.com",
-        "issues_url" => "http://example.com/issues"
+        "issues_url" => "http://example.com/issues",
+        "privacy" => true
       }
       expect(metadata.attribute("/db/mysql/databases", attrs)).to eq(attrs)
     end
@@ -386,6 +511,18 @@ describe Chef::Cookbook::Metadata do
       }.to raise_error(ArgumentError)
     end
 
+    it "should not accept anything but true or false for the privacy flag" do
+      expect {
+        metadata.attribute("db/mysql/databases", :privacy => true)
+      }.not_to raise_error
+      expect {
+        metadata.attribute("db/mysql/databases", :privacy => false)
+      }.not_to raise_error
+      expect {
+        metadata.attribute("db/mysql/databases", :privacy => 'true')
+      }.to raise_error(ArgumentError)
+    end
+
     it "should not accept anything but an array of strings for choice" do
       expect {
         metadata.attribute("db/mysql/databases", :choice => ['dedicated', 'shared'])
@@ -651,9 +788,14 @@ describe Chef::Cookbook::Metadata do
       metadata.attribute "bizspark/has_login",
         :display_name => "You have nothing"
       metadata.version "1.2.3"
+      metadata.chef_version ">= 11.14.2", "< 11.18.10"
+      metadata.chef_version ">= 12.2.1", "< 12.5.1"
+      metadata.ohai_version ">= 7.1.0", "< 7.5.0"
+      metadata.ohai_version ">= 8.0.1", "< 8.6.0"
     end
 
     it "should produce the same output from to_json and Chef::JSONCompat" do
+      # XXX: fairly certain this is testing ruby method dispatch
       expect(metadata.to_json).to eq(Chef::JSONCompat.to_json(metadata))
     end
 
@@ -684,11 +826,21 @@ describe Chef::Cookbook::Metadata do
         version
         source_url
         issues_url
+        privacy
       }.each do |t|
         it "should include '#{t}'" do
           expect(deserialized_metadata[t]).to eq(metadata.send(t.to_sym))
         end
       end
+
+      %w{
+        ohai_versions
+        chef_versions
+      }.each do |t|
+        it "should include '#{t}'" do
+          expect(deserialized_metadata[t]).to eq(metadata.gem_requirements_to_array(*metadata.send(t.to_sym)))
+        end
+      end
     end
 
     describe "deserialize" do
@@ -719,6 +871,9 @@ describe Chef::Cookbook::Metadata do
         version
         source_url
         issues_url
+        privacy
+        chef_versions
+        ohai_versions
       }.each do |t|
         it "should match '#{t}'" do
           expect(deserialized_metadata.send(t.to_sym)).to eq(metadata.send(t.to_sym))
diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb
index 471fc01..efdb5b7 100644
--- a/spec/unit/cookbook/syntax_check_spec.rb
+++ b/spec/unit/cookbook/syntax_check_spec.rb
@@ -21,7 +21,7 @@ require "chef/cookbook/syntax_check"
 
 describe Chef::Cookbook::SyntaxCheck do
   before do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
   end
 
   let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'cookbooks', 'openldap') }
@@ -53,7 +53,10 @@ describe Chef::Cookbook::SyntaxCheck do
     @ruby_files = @attr_files + @libr_files + @defn_files + @recipes + [File.join(cookbook_path, "metadata.rb")]
     basenames = %w{ helpers_via_partial_test.erb
                     helper_test.erb
+                    helpers.erb
                     openldap_stuff.conf.erb
+                    nested_openldap_partials.erb
+                    nested_partial.erb
                     openldap_variable_stuff.conf.erb
                     test.erb
                     some_windows_line_endings.erb
diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb
index 45a985b..b1384bf 100644
--- a/spec/unit/cookbook_loader_spec.rb
+++ b/spec/unit/cookbook_loader_spec.rb
@@ -20,7 +20,7 @@ require 'spec_helper'
 
 describe Chef::CookbookLoader do
   before do
-    allow(Chef::Platform).to receive(:windows?) {false}
+    allow(ChefConfig).to receive(:windows?) {false}
   end
   let(:repo_paths) do
     [
diff --git a/spec/unit/cookbook_site_streaming_uploader_spec.rb b/spec/unit/cookbook_site_streaming_uploader_spec.rb
index ef0f649..0041a14 100644
--- a/spec/unit/cookbook_site_streaming_uploader_spec.rb
+++ b/spec/unit/cookbook_site_streaming_uploader_spec.rb
@@ -121,27 +121,6 @@ describe Chef::CookbookSiteStreamingUploader do
       })
     end
 
-    describe "http verify mode" do
-      before do
-        @uri = "https://cookbooks.dummy.com/api/v1/cookbooks"
-        uri_info = URI.parse(@uri)
-        @http = Net::HTTP.new(uri_info.host, uri_info.port)
-        expect(Net::HTTP).to receive(:new).with(uri_info.host, uri_info.port).and_return(@http)
-      end
-
-      it "should be VERIFY_NONE when ssl_verify_mode is :verify_none" do
-        Chef::Config[:ssl_verify_mode] = :verify_none
-        Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename)
-        expect(@http.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE)
-      end
-
-      it "should be VERIFY_PEER when ssl_verify_mode is :verify_peer" do
-        Chef::Config[:ssl_verify_mode] = :verify_peer
-        Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename)
-        expect(@http.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER)
-      end
-    end
-
   end # make_request
 
   describe "StreamPart" do
diff --git a/spec/unit/cookbook_spec.rb b/spec/unit/cookbook_spec.rb
index 7b3cda2..f36b031 100644
--- a/spec/unit/cookbook_spec.rb
+++ b/spec/unit/cookbook_spec.rb
@@ -59,15 +59,6 @@ describe Chef::CookbookVersion do
     expect(@cookbook.fully_qualified_recipe_names.include?("openldap::three")).to eq(true)
   end
 
-  it "should find a preferred file" do
-    skip
-  end
-
-  it "should not return an unchanged preferred file" do
-    pending
-    expect(@cookbook.preferred_filename(@node, :files, 'a-filename', 'the-checksum')).to be_nil
-  end
-
   it "should raise an ArgumentException if you try to load a bad recipe name" do
     expect { @cookbook.load_recipe("doesnt_exist", @node) }.to raise_error(ArgumentError)
   end
diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb
index 440dd9d..2bccddc 100644
--- a/spec/unit/cookbook_version_spec.rb
+++ b/spec/unit/cookbook_version_spec.rb
@@ -306,26 +306,6 @@ describe Chef::CookbookVersion do
 
     subject(:cbv) { Chef::CookbookVersion.new("version validation", '/tmp/blah') }
 
-    describe "HTTP Resource behaviors", pending: "will be deprected when CookbookManifest API is stablized" do
-
-      it "errors on #save_url" do
-        expect { cbv.save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
-      end
-
-      it "errors on #force_save_url" do
-        expect { cbv.force_save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
-      end
-
-      it "errors on #to_hash" do
-        expect { cbv.to_hash }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
-      end
-
-      it "errors on #to_json" do
-        expect { cbv.to_json }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
-      end
-
-    end
-
     it "errors on #status and #status=" do
       expect { cbv.status = :wat }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
       expect { cbv.status }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
@@ -356,7 +336,7 @@ describe Chef::CookbookVersion do
     end
 
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) { Chef::CookbookVersion.new("tatft", '/tmp/blah') }
     end
 
diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb
index 4348252..497817e 100644
--- a/spec/unit/data_bag_item_spec.rb
+++ b/spec/unit/data_bag_item_spec.rb
@@ -193,7 +193,7 @@ describe Chef::DataBagItem do
       expect(deserial["snooze"]).to eq({ "finally" => "world_will" })
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) { data_bag_item }
     end
   end
diff --git a/spec/unit/data_bag_spec.rb b/spec/unit/data_bag_spec.rb
index f6db1e2..13b835d 100644
--- a/spec/unit/data_bag_spec.rb
+++ b/spec/unit/data_bag_spec.rb
@@ -22,7 +22,7 @@ require 'chef/data_bag'
 describe Chef::DataBag do
   before(:each) do
     @data_bag = Chef::DataBag.new
-    allow(Chef::Platform)::to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
   end
 
   describe "initialize" do
@@ -73,7 +73,7 @@ describe Chef::DataBag do
         expect(@deserial.send(t.to_sym)).to eq(@data_bag.send(t.to_sym))
       end
 
-      include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+      include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
         let(:jsonable) { @data_bag }
       end
     end
diff --git a/spec/unit/deprecation_spec.rb b/spec/unit/deprecation_spec.rb
index f824cb7..674de5e 100644
--- a/spec/unit/deprecation_spec.rb
+++ b/spec/unit/deprecation_spec.rb
@@ -65,19 +65,16 @@ describe Chef::Deprecation do
     end
 
     context 'deprecation warning messages' do
-      before(:each) do
-        @warning_output = [ ]
-        allow(Chef::Log).to receive(:warn) { |msg| @warning_output << msg }
-      end
+      RSpec::Matchers.define_negated_matcher :a_non_empty_array, :be_empty
 
       it 'should be enabled for deprecated methods' do
+        expect(Chef::Log).to receive(:warn).with(a_non_empty_array)
         TestClass.new.deprecated_method(10)
-        expect(@warning_output).not_to be_empty
       end
 
       it 'should contain stack trace' do
+        expect(Chef::Log).to receive(:warn).with(a_string_including(".rb"))
         TestClass.new.deprecated_method(10)
-        expect(@warning_output.join("").include?(".rb")).to be_truthy
       end
     end
 
@@ -95,4 +92,59 @@ describe Chef::Deprecation do
     expect { test_instance.deprecated_method(10) }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
   end
 
+  context "When a class has deprecated_attr, _reader and _writer" do
+    before(:context) do
+      class DeprecatedAttrTest
+        extend Chef::Mixin::Deprecation
+        def initialize
+          @a = @r = @w = 1
+        end
+        deprecated_attr :a, "a"
+        deprecated_attr_reader :r, "r"
+        deprecated_attr_writer :w, "w"
+      end
+    end
+
+    it "The deprecated_attr emits warnings" do
+      test = DeprecatedAttrTest.new
+      expect { test.a = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+      expect { test.a }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+    end
+
+    it "The deprecated_attr_writer emits warnings, and does not create a reader" do
+      test = DeprecatedAttrTest.new
+      expect { test.w = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+      expect { test.w }.to raise_error(NoMethodError)
+    end
+
+    it "The deprecated_attr_reader emits warnings, and does not create a writer" do
+      test = DeprecatedAttrTest.new
+      expect { test.r = 10 }.to raise_error(NoMethodError)
+      expect { test.r }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+    end
+
+    context "With deprecation warnings not throwing exceptions" do
+      before do
+        Chef::Config[:treat_deprecation_warnings_as_errors] = false
+      end
+
+      it "The deprecated_attr can be written to and read from" do
+        test = DeprecatedAttrTest.new
+        test.a = 10
+        expect(test.a).to eq 10
+      end
+
+      it "The deprecated_attr_reader can be read from" do
+        test = DeprecatedAttrTest.new
+        expect(test.r).to eq 1
+      end
+
+      it "The deprecated_attr_writer can be written to" do
+        test = DeprecatedAttrTest.new
+        test.w = 10
+        expect(test.instance_eval { @w }).to eq 10
+      end
+    end
+  end
+
 end
diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb
index 0f22887..6705820 100644
--- a/spec/unit/dsl/reboot_pending_spec.rb
+++ b/spec/unit/dsl/reboot_pending_spec.rb
@@ -46,15 +46,21 @@ describe Chef::DSL::RebootPending do
         end
   
         it 'should return true if key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired" exists' do
-          allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired').and_return(true)
+          allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending').and_return(true)
           expect(recipe.reboot_pending?).to be_truthy
         end
   
-        it 'should return true if value "HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile" contains specific data' do
-          allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(true)
-          allow(recipe).to receive(:registry_get_values).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(
-                [{:name => "Flags", :type => :dword, :data => 3}])
-          expect(recipe.reboot_pending?).to be_truthy
+        context "version is server 2003" do
+          before do
+            allow(Chef::Platform).to receive(:windows_server_2003?).and_return(true)
+          end
+
+          it 'should return true if value "HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile" contains specific data on 2k3' do
+            allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(true)
+            allow(recipe).to receive(:registry_get_values).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(
+                  [{:name => "Flags", :type => :dword, :data => 3}])
+            expect(recipe.reboot_pending?).to be_truthy
+          end
         end
       end
   
diff --git a/spec/unit/dsl/resources_spec.rb b/spec/unit/dsl/resources_spec.rb
new file mode 100644
index 0000000..581c835
--- /dev/null
+++ b/spec/unit/dsl/resources_spec.rb
@@ -0,0 +1,85 @@
+#
+# Author:: Noah Kantrowitz (<noah at coderanger.net>)
+# Copyright:: Copyright (c) 2015 Noah Kantrowitz
+# 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 'spec_helper'
+require 'chef/dsl/resources'
+
+describe Chef::DSL::Resources do
+  let(:declared_resources) { [] }
+  let(:test_class) do
+    r = declared_resources
+    Class.new do
+      include Chef::DSL::Resources
+      define_method(:declare_resource) do |dsl_name, name, _created_at, &_block|
+        r << [dsl_name, name]
+      end
+    end
+  end
+  subject { declared_resources }
+  after do
+    # Always clean up after ourselves.
+    described_class.remove_resource_dsl(:test_resource)
+  end
+
+  context 'with a resource added' do
+    before do
+      Chef::DSL::Resources.add_resource_dsl(:test_resource)
+      test_class.new.instance_eval do
+        test_resource 'test_name' do
+        end
+      end
+    end
+    it { is_expected.to eq [[:test_resource, 'test_name']]}
+  end
+
+  context 'with no resource added' do
+    subject do
+      test_class.new.instance_eval do
+        test_resource 'test_name' do
+        end
+      end
+    end
+
+    it { expect { subject }.to raise_error NoMethodError }
+  end
+
+  context 'with a resource added and removed' do
+    before do
+      Chef::DSL::Resources.add_resource_dsl(:test_resource)
+      Chef::DSL::Resources.remove_resource_dsl(:test_resource)
+    end
+    subject do
+      test_class.new.instance_eval do
+        test_resource 'test_name' do
+        end
+      end
+    end
+
+    it { expect { subject }.to raise_error NoMethodError }
+  end
+
+  context 'with a nameless resource' do
+    before do
+      Chef::DSL::Resources.add_resource_dsl(:test_resource)
+      test_class.new.instance_eval do
+        test_resource { }
+      end
+    end
+    it { is_expected.to eq [[:test_resource, nil]]}
+  end
+end
diff --git a/spec/unit/environment_spec.rb b/spec/unit/environment_spec.rb
index ee3b8b2..64617e0 100644
--- a/spec/unit/environment_spec.rb
+++ b/spec/unit/environment_spec.rb
@@ -208,7 +208,7 @@ describe Chef::Environment do
       expect(@json).to match(/"chef_type":"environment"/)
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) { @environment }
     end
   end
diff --git a/spec/unit/event_dispatch/dispatcher_spec.rb b/spec/unit/event_dispatch/dispatcher_spec.rb
new file mode 100644
index 0000000..5a06e1d
--- /dev/null
+++ b/spec/unit/event_dispatch/dispatcher_spec.rb
@@ -0,0 +1,123 @@
+#
+# Author:: Daniel DeLeo (<dan at chef.io>)
+#
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/event_dispatch/dispatcher'
+
+describe Chef::EventDispatch::Dispatcher do
+
+  subject(:dispatcher) { Chef::EventDispatch::Dispatcher.new }
+
+  let(:event_sink) { instance_double("Chef::EventDispatch::Base") }
+
+  it "has no subscribers by default" do
+    expect(dispatcher.subscribers).to be_empty
+  end
+
+  context "when an event sink is registered" do
+
+    before do
+      dispatcher.register(event_sink)
+    end
+
+    it "it has the event sink as a subscriber" do
+      expect(dispatcher.subscribers.size).to eq(1)
+      expect(dispatcher.subscribers.first).to eq(event_sink)
+    end
+
+    it "forwards events to the subscribed event sink" do
+      # the events all have different arity and such so we just hit a few different events:
+
+      expect(event_sink).to receive(:run_start).with("12.4.0")
+      dispatcher.run_start("12.4.0")
+
+      cookbook_version = double("cookbook_version")
+      expect(event_sink).to receive(:synchronized_cookbook).with("apache2", cookbook_version)
+      dispatcher.synchronized_cookbook("apache2", cookbook_version)
+
+      exception = StandardError.new("foo")
+      expect(event_sink).to receive(:recipe_file_load_failed).with("/path/to/file.rb", exception)
+      dispatcher.recipe_file_load_failed("/path/to/file.rb", exception)
+    end
+
+    context "when an event sink has fewer arguments for an event" do
+      # Can't use a double because they don't report arity correctly.
+      let(:event_sink) do
+        Class.new(Chef::EventDispatch::Base) do
+          attr_reader :synchronized_cookbook_args
+          def synchronized_cookbook(cookbook_name)
+            @synchronized_cookbook_args = [cookbook_name]
+          end
+        end.new
+      end
+
+      it "trims the arugment list" do
+        cookbook_version = double("cookbook_version")
+        dispatcher.synchronized_cookbook("apache2", cookbook_version)
+        expect(event_sink.synchronized_cookbook_args).to eq ["apache2"]
+      end
+    end
+  end
+
+
+  context "when two event sinks have different arguments for an event" do
+    let(:event_sink_1) do
+      Class.new(Chef::EventDispatch::Base) do
+        attr_reader :synchronized_cookbook_args
+        def synchronized_cookbook(cookbook_name)
+          @synchronized_cookbook_args = [cookbook_name]
+        end
+      end.new
+    end
+    let(:event_sink_2) do
+      Class.new(Chef::EventDispatch::Base) do
+        attr_reader :synchronized_cookbook_args
+        def synchronized_cookbook(cookbook_name, cookbook)
+          @synchronized_cookbook_args = [cookbook_name, cookbook]
+        end
+      end.new
+    end
+
+    context "and the one with fewer arguments comes first" do
+      before do
+        dispatcher.register(event_sink_1)
+        dispatcher.register(event_sink_2)
+      end
+      it "trims the arugment list" do
+        cookbook_version = double("cookbook_version")
+        dispatcher.synchronized_cookbook("apache2", cookbook_version)
+        expect(event_sink_1.synchronized_cookbook_args).to eq ["apache2"]
+        expect(event_sink_2.synchronized_cookbook_args).to eq ["apache2", cookbook_version]
+      end
+    end
+
+    context "and the one with fewer arguments comes last" do
+      before do
+        dispatcher.register(event_sink_2)
+        dispatcher.register(event_sink_1)
+      end
+      it "trims the arugment list" do
+        cookbook_version = double("cookbook_version")
+        dispatcher.synchronized_cookbook("apache2", cookbook_version)
+        expect(event_sink_1.synchronized_cookbook_args).to eq ["apache2"]
+        expect(event_sink_2.synchronized_cookbook_args).to eq ["apache2", cookbook_version]
+      end
+    end
+  end
+end
diff --git a/spec/unit/event_dispatch/dsl_spec.rb b/spec/unit/event_dispatch/dsl_spec.rb
new file mode 100644
index 0000000..0f7adce
--- /dev/null
+++ b/spec/unit/event_dispatch/dsl_spec.rb
@@ -0,0 +1,83 @@
+#
+# Author:: Ranjib Dey (<ranjib at linux.com>)
+#
+# Copyright:: Copyright (c) 2015 Ranjib Dey
+# 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 'spec_helper'
+require 'chef/event_dispatch/dsl'
+
+describe Chef::EventDispatch::DSL do
+  let(:events) do
+    Chef::EventDispatch::Dispatcher.new
+  end
+
+  let(:run_context) do
+    Chef::RunContext.new(Chef::Node.new, nil, events)
+  end
+
+  before do
+    Chef.set_run_context(run_context)
+  end
+
+  subject{ described_class.new('test') }
+
+  it 'set handler name' do
+    subject.on(:run_started) {}
+    expect(events.subscribers.first.name).to eq('test')
+  end
+
+  it 'raise error when invalid event type is supplied' do
+    expect do
+      subject.on(:foo_bar) {}
+    end.to raise_error(Chef::Exceptions::InvalidEventType)
+  end
+
+  it 'register user hooks against valid event type' do
+    subject.on(:run_failed) {'testhook'}
+    expect(events.subscribers.first.run_failed).to eq('testhook')
+  end
+
+  it 'preserve state across event hooks' do
+    calls = []
+    Chef.event_handler do
+      on :resource_updated do
+        calls << :updated
+      end
+      on :resource_action_start do
+        calls << :started
+      end
+    end
+    resource = Chef::Resource::RubyBlock.new('foo', run_context)
+    resource.block { }
+    resource.run_action(:run)
+    expect(calls).to eq([:started, :updated])
+  end
+
+  it 'preserve instance variables across handler callbacks' do
+    Chef.event_handler do
+      on :resource_action_start do
+        @ivar = [1]
+      end
+      on :resource_updated do
+        @ivar << 2
+      end
+    end
+    resource = Chef::Resource::RubyBlock.new('foo', run_context)
+    resource.block { }
+    resource.run_action(:run)
+    expect(events.subscribers.first.instance_variable_get(:@ivar)).to eq([1, 2])
+  end
+end
diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb
index d35ecc8..85c54aa 100644
--- a/spec/unit/exceptions_spec.rb
+++ b/spec/unit/exceptions_spec.rb
@@ -76,7 +76,7 @@ describe Chef::Exceptions do
     end
 
     if exception.methods.include?(:to_json)
-      include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+      include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
         let(:jsonable) { exception }
       end
     end
@@ -113,7 +113,7 @@ describe Chef::Exceptions do
     context "initialized with 1 error and nil" do
       let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), nil)  }
       let(:num_errors) { 1 }
-      let(:backtrace) { ["1) RuntimeError -  foo", ""] }
+      let(:backtrace) { ["1) RuntimeError -  foo"] }
 
       include_examples "RunFailedWrappingError expectations"
     end
@@ -121,7 +121,7 @@ describe Chef::Exceptions do
     context "initialized with 2 errors" do
       let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), RuntimeError.new("bar"))  }
       let(:num_errors) { 2 }
-      let(:backtrace) { ["1) RuntimeError -  foo", "", "2) RuntimeError -  bar", ""] }
+      let(:backtrace) { ["1) RuntimeError -  foo", "", "2) RuntimeError -  bar"] }
 
       include_examples "RunFailedWrappingError expectations"
     end
diff --git a/spec/unit/file_content_management/deploy/mv_windows_spec.rb b/spec/unit/file_content_management/deploy/mv_windows_spec.rb
index c52001c..2d1981b 100644
--- a/spec/unit/file_content_management/deploy/mv_windows_spec.rb
+++ b/spec/unit/file_content_management/deploy/mv_windows_spec.rb
@@ -115,6 +115,66 @@ describe Chef::FileContentManagement::Deploy::MvWindows do
 
       end
 
+      context "and the target file has null dacl and sacl" do
+
+        before do
+          allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(true)
+          allow(target_file_security_descriptor).to receive(:dacl).and_return(nil)
+          allow(target_file_security_descriptor).to receive(:dacl_inherits?).and_return(false)
+
+          allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(true)
+          allow(target_file_security_descriptor).to receive(:sacl).and_return(nil)
+          allow(target_file_security_descriptor).to receive(:sacl_inherits?).and_return(false)
+
+          expect(updated_target_security_object).to receive(:set_dacl).with(nil, false)
+          expect(updated_target_security_object).to receive(:set_sacl).with(nil, false)
+        end
+
+
+        it "fixes up permissions and moves the file into place" do
+          content_deployer.deploy(staging_file_path, target_file_path)
+        end
+
+      end
+
+      context "and the target has an empty dacl and sacl" do
+        let(:original_target_file_dacl) { [] }
+        let(:original_target_file_sacl) { [] }
+
+        let(:empty_dacl) { double("Windows ACL with no dacl ACEs") }
+        let(:empty_sacl) { double("Windows ACL with no sacl ACEs") }
+
+        before do
+          allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(true)
+          allow(target_file_security_descriptor).to receive(:dacl_inherits?).and_return(false)
+
+          allow(target_file_security_descriptor).to receive(:dacl).and_return(original_target_file_dacl)
+          expect(Chef::ReservedNames::Win32::Security::ACL).
+            to receive(:create).
+            with([]).
+            and_return(empty_dacl)
+
+
+          allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(true)
+          allow(target_file_security_descriptor).to receive(:sacl_inherits?).and_return(false)
+
+          allow(target_file_security_descriptor).to receive(:sacl).and_return(original_target_file_sacl)
+          expect(Chef::ReservedNames::Win32::Security::ACL).
+            to receive(:create).
+            with([]).
+            and_return(empty_sacl)
+
+
+          expect(updated_target_security_object).to receive(:set_dacl).with(empty_dacl, false)
+          expect(updated_target_security_object).to receive(:set_sacl).with(empty_sacl, false)
+        end
+
+
+        it "fixes up permissions and moves the file into place" do
+          content_deployer.deploy(staging_file_path, target_file_path)
+        end
+      end
+
       context "and the target has a dacl and sacl" do
         let(:inherited_dacl_ace) { double("Windows dacl ace (inherited)", :inherited? => true) }
         let(:not_inherited_dacl_ace) { double("Windows dacl ace (not inherited)", :inherited? => false) }
diff --git a/spec/unit/formatters/doc_spec.rb b/spec/unit/formatters/doc_spec.rb
new file mode 100644
index 0000000..7266afc
--- /dev/null
+++ b/spec/unit/formatters/doc_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Daniel DeLeo (<dan at chef.io>)
+#
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+describe Chef::Formatters::Base do
+
+  let(:out) { StringIO.new }
+  let(:err) { StringIO.new }
+
+  subject(:formatter) { Chef::Formatters::Doc.new(out, err) }
+
+  it "prints a policyfile's name and revision ID" do
+    minimal_policyfile = {
+      "revision_id"=> "613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073",
+      "name"=> "jenkins",
+      "run_list"=> [
+        "recipe[apt::default]",
+        "recipe[java::default]",
+        "recipe[jenkins::master]",
+        "recipe[policyfile_demo::default]"
+      ],
+      "cookbook_locks"=> { }
+    }
+
+    formatter.policyfile_loaded(minimal_policyfile)
+    expect(out.string).to include("Using policy 'jenkins' at revision '613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073'")
+  end
+
+  it "prints cookbook name and version" do
+    cookbook_version = double(name: "apache2", version: "1.2.3")
+    formatter.synchronized_cookbook("apache2", cookbook_version)
+    expect(out.string).to include("- apache2 (1.2.3")
+  end
+
+  it "prints only seconds when elapsed time is less than 60 seconds" do
+    @now = Time.now
+    allow(Time).to receive(:now).and_return(@now, @now + 10.0)
+    formatter.run_completed(nil)
+    expect(formatter.elapsed_time).to eql(10.0)
+    expect(formatter.pretty_elapsed_time).to include("10 seconds")
+    expect(formatter.pretty_elapsed_time).not_to include("minutes")
+    expect(formatter.pretty_elapsed_time).not_to include("hours")
+  end
+
+  it "prints minutes and seconds when elapsed time is more than 60 seconds" do
+    @now = Time.now
+    allow(Time).to receive(:now).and_return(@now, @now + 610.0)
+    formatter.run_completed(nil)
+    expect(formatter.elapsed_time).to eql(610.0)
+    expect(formatter.pretty_elapsed_time).to include("10 minutes 10 seconds")
+    expect(formatter.pretty_elapsed_time).not_to include("hours")
+  end
+
+  it "prints hours, minutes and seconds when elapsed time is more than 3600 seconds" do
+    @now = Time.now
+    allow(Time).to receive(:now).and_return(@now, @now + 36610.0)
+    formatter.run_completed(nil)
+    expect(formatter.elapsed_time).to eql(36610.0)
+    expect(formatter.pretty_elapsed_time).to include("10 hours 10 minutes 10 seconds")
+  end
+end
diff --git a/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb
new file mode 100644
index 0000000..b8c2de2
--- /dev/null
+++ b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/formatters/error_inspectors/api_error_formatting'
+
+describe Chef::Formatters::APIErrorFormatting do
+  let(:class_instance) { (Class.new { include Chef::Formatters::APIErrorFormatting }).new }
+  let(:error_description) { instance_double(Chef::Formatters::ErrorDescription) }
+  let(:response) { double("response") }
+  before do
+    allow(response).to receive(:body)
+  end
+
+
+  context "when describe_406_error is called" do
+    context "when response['x-ops-server-api-version'] exists" do
+      let(:min_version) { "2" }
+      let(:max_version) { "5" }
+      let(:request_version) { "30" }
+      let(:return_hash) {
+        {
+          "min_version" => min_version,
+          "max_version" => max_version,
+          "request_version" => request_version
+        }
+      }
+
+      before do
+        # mock out the header
+        allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash))
+      end
+
+      it "prints an error about client and server API version incompatibility with a min API version" do
+        expect(error_description).to receive(:section).with("Incompatible server API version:",/a min API version of #{min_version}/)
+        class_instance.describe_406_error(error_description, response)
+      end
+
+      it "prints an error about client and server API version incompatibility with a max API version" do
+        expect(error_description).to receive(:section).with("Incompatible server API version:",/a max API version of #{max_version}/)
+        class_instance.describe_406_error(error_description, response)
+      end
+
+      it "prints an error describing the request API version" do
+        expect(error_description).to receive(:section).with("Incompatible server API version:",/a request with an API version of #{request_version}/)
+        class_instance.describe_406_error(error_description, response)
+      end
+    end
+
+    context "when response.body['error'] != 'invalid-x-ops-server-api-version'" do
+
+      before do
+        allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(nil)
+      end
+
+      it "forwards the error_description to describe_http_error" do
+        expect(class_instance).to receive(:describe_http_error).with(error_description)
+        class_instance.describe_406_error(error_description, response)
+      end
+    end
+  end
+end
diff --git a/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb b/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
index ac19e91..3c8d5df 100644
--- a/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
+++ b/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
@@ -37,69 +37,148 @@ end
 E
 
 describe Chef::Formatters::ErrorInspectors::CompileErrorInspector do
-  before do
-    @node_name = "test-node.example.com"
-    @description = Chef::Formatters::ErrorDescription.new("Error Evaluating File:")
-    @exception = NoMethodError.new("undefined method `this_is_not_a_valid_method' for Chef::Resource::File")
 
-    @outputter = Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR)
-    #@outputter = Chef::Formatters::IndentableOutputStream.new(STDOUT, STDERR)
-  end
+  let(:node_name) { "test-node.example.com" }
 
-  describe "when scrubbing backtraces" do
-    it "shows backtrace lines from cookbook files" do
-      # Error inspector originally used file_cache_path which is incorrect on
-      # chef-solo. Using cookbook_path should do the right thing for client and
-      # solo.
-      allow(Chef::Config).to receive(:cookbook_path).and_return([ "/home/someuser/dev-laptop/cookbooks" ])
-      @trace = [
-        "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
-        "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
-        "/home/someuser/.multiruby/gems/chef/lib/chef/client.rb:123:in `run'"
-      ]
-      @exception.set_backtrace(@trace)
-      @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb"
-      @inspector = described_class.new(@path, @exception)
+  let(:description) { Chef::Formatters::ErrorDescription.new("Error Evaluating File:") }
 
-      @expected_filtered_trace = [
-        "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
-        "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
-      ]
-      expect(@inspector.filtered_bt).to eq(@expected_filtered_trace)
-    end
+  let(:exception) do
+    e = NoMethodError.new("undefined method `this_is_not_a_valid_method' for Chef::Resource::File")
+    e.set_backtrace(trace)
+    e
   end
 
-  describe "when explaining an error in the compile phase" do
-    before do
-      allow(Chef::Config).to receive(:cookbook_path).and_return([ "/var/chef/cache/cookbooks" ])
-      recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
-      expect(IO).to receive(:readlines).with("/var/chef/cache/cookbooks/syntax-err/recipes/default.rb").and_return(recipe_lines)
-      @trace = [
-        "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
-        "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
-        "/usr/local/lib/ruby/gems/chef/lib/chef/client.rb:123:in `run'" # should not display
-      ]
-      @exception.set_backtrace(@trace)
-      @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb"
-      @inspector = described_class.new(@path, @exception)
-      @inspector.add_explanation(@description)
+  # Change to $stdout to print error messages for manual inspection
+  let(:stdout) { StringIO.new }
+
+  let(:outputter) { Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) }
+
+  subject(:inspector) { described_class.new(path_to_failed_file, exception) }
+
+  describe "finding the code responsible for the error" do
+
+    context "when the stacktrace includes cookbook files" do
+
+      let(:trace) do
+        [
+          "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
+          "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
+          "/home/someuser/.multiruby/gems/chef/lib/chef/client.rb:123:in `run'"
+        ]
+      end
+
+      let(:expected_filtered_trace) do
+        [
+          "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
+          "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
+        ]
+      end
+
+      let(:path_to_failed_file) { "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb" }
+
+      before do
+        # Error inspector originally used file_cache_path which is incorrect on
+        # chef-solo. Using cookbook_path should do the right thing for client and
+        # solo.
+        allow(Chef::Config).to receive(:cookbook_path).and_return([ "/home/someuser/dev-laptop/cookbooks" ])
+      end
+
+      describe "when scrubbing backtraces" do
+        it "shows backtrace lines from cookbook files" do
+          expect(inspector.filtered_bt).to eq(expected_filtered_trace)
+        end
+      end
+
+      describe "when explaining an error in the compile phase" do
+        before do
+          recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
+          expect(IO).to receive(:readlines).with(path_to_failed_file).and_return(recipe_lines)
+          inspector.add_explanation(description)
+        end
+
+        it "reports the error was not located within cookbooks" do
+          expect(inspector.found_error_in_cookbooks?).to be(true)
+        end
+
+        it "finds the line number of the error from the stacktrace" do
+          expect(inspector.culprit_line).to eq(14)
+        end
+
+        it "prints a pretty message" do
+          description.display(outputter)
+        end
+      end
     end
 
-    it "finds the line number of the error from the stacktrace" do
-      expect(@inspector.culprit_line).to eq(14)
+    context "when the error is a RuntimeError about frozen object" do
+      let(:exception) do
+        e = RuntimeError.new("can't modify frozen Array")
+        e.set_backtrace(trace)
+        e
+      end
+
+      let(:path_to_failed_file) { "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb" }
+
+      let(:trace) do
+        [
+          "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb:2:in `block in from_file'",
+          "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb:1:in `from_file'"
+        ]
+      end
+
+      describe "when explaining a runtime error in the compile phase" do
+        it "correctly detects RuntimeError for frozen objects" do
+          expect(inspector.exception_message_modifying_frozen?).to be(true)
+        end
+
+        # could also test for description.section to be called, but would have
+        # to adjust every other test to begin using a test double for description
+      end
     end
 
-    it "prints a pretty message" do
-      @description.display(@outputter)
+    context "when the error does not contain any lines from cookbooks" do
+
+      let(:trace) do
+        [
+          "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'",
+          "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'",
+          "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `call'",
+          "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `block (2 levels) in foreach_cookbook_load_segment'",
+          "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `each'",
+          "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `block in foreach_cookbook_load_segment'",
+          "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `each'",
+          "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `foreach_cookbook_load_segment'",
+          "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:137:in `load_libraries'"
+        ]
+      end
+
+      let(:exception) do
+        e = Chef::Exceptions::RecipeNotFound.new("recipe nope:nope not found")
+        e.set_backtrace(trace)
+        e
+      end
+
+      let(:path_to_failed_file) { nil }
+
+      it "gives a full, non-filtered trace" do
+        expect(inspector.filtered_bt).to eq(trace)
+      end
+
+      it "does not error when displaying the error" do
+        expect { description.display(outputter) }.to_not raise_error
+      end
+
+      it "reports the error was not located within cookbooks" do
+        expect(inspector.found_error_in_cookbooks?).to be(false)
+      end
+
     end
   end
 
   describe "when explaining an error on windows" do
-    before do
-      allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ])
-      recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
-      expect(IO).to receive(:readlines).at_least(1).times.with(/:\/opscode\/chef\/var\/cache\/cookbooks\/foo\/recipes\/default.rb/).and_return(recipe_lines)
-      @trace = [
+
+    let(:trace_with_upcase_drive) do
+      [
         "C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb:14 in `from_file'",
         "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'",
         "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'",
@@ -122,81 +201,65 @@ describe Chef::Formatters::ErrorInspectors::CompileErrorInspector do
         "C:/opscode/chef/bin/chef-client:19:in `load'",
         "C:/opscode/chef/bin/chef-client:19:in `<main>'"
       ]
-      @exception.set_backtrace(@trace)
-      @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb"
-      @inspector = described_class.new(@path, @exception)
-      @inspector.add_explanation(@description)
     end
 
+    let(:trace) { trace_with_upcase_drive }
+
+    let(:path_to_failed_file) { "/var/cache/cookbooks/foo/recipes/default.rb" }
 
-    describe "and examining the stack trace for a recipe" do
-      it "find the culprit recipe name when the drive letter is upper case" do
-        expect(@inspector.culprit_file).to eq("C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
+    before do
+      allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ])
+      recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
+      expect(IO).to receive(:readlines).at_least(1).times.with(full_path_to_failed_file).and_return(recipe_lines)
+      inspector.add_explanation(description)
+    end
+
+    context "when the drive letter in the path is uppercase" do
+
+      let(:full_path_to_failed_file) { "C:/opscode/chef#{path_to_failed_file}" }
+
+      it "reports the error was not located within cookbooks" do
+        expect(inspector.found_error_in_cookbooks?).to be(true)
       end
 
-      it "find the culprit recipe name when the drive letter is lower case" do
-        @trace.each { |line| line.gsub!(/^C:/, "c:") }
-        @exception.set_backtrace(@trace)
-        @inspector = described_class.new(@path, @exception)
-        @inspector.add_explanation(@description)
-        expect(@inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
+      it "finds the culprit recipe name" do
+        expect(inspector.culprit_file).to eq("C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
       end
-    end
 
-    it "finds the line number of the error from the stack trace" do
-      expect(@inspector.culprit_line).to eq(14)
-    end
+      it "finds the line number of the error from the stack trace" do
+        expect(inspector.culprit_line).to eq(14)
+      end
 
-    it "prints a pretty message" do
-      @description.display(@outputter)
+      it "prints a pretty message" do
+        description.display(outputter)
+      end
     end
-  end
 
-  describe "when explaining an error on windows, and the backtrace lowercases the drive letter" do
-    before do
-      allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ])
-      recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
-      expect(IO).to receive(:readlines).with("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb").and_return(recipe_lines)
-      @trace = [
-        "c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb:14 in `from_file'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `call'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `block (2 levels) in foreach_cookbook_load_segment'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `each'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `block in foreach_cookbook_load_segment'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `each'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `foreach_cookbook_load_segment'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:137:in `load_libraries'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:62:in `load'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:198:in `setup_run_context'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:418:in `do_run'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:176:in `run'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:283:in `block in run_application'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:270:in `loop'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:270:in `run_application'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application.rb:70:in `run'",
-        "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/bin/chef-client:26:in `<top (required)>'",
-        "c:/opscode/chef/bin/chef-client:19:in `load'",
-        "c:/opscode/chef/bin/chef-client:19:in `<main>'"
-      ]
-      @exception.set_backtrace(@trace)
-      @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb"
-      @inspector = described_class.new(@path, @exception)
-      @inspector.add_explanation(@description)
-    end
+    context "when the drive letter in the path is lowercase" do
 
-    it "finds the culprit recipe name from the stacktrace" do
-      expect(@inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
-    end
+      let(:trace) do
+        trace_with_upcase_drive.map { |line| line.gsub(/^C:/, "c:") }
+      end
 
-    it "finds the line number of the error from the stack trace" do
-      expect(@inspector.culprit_line).to eq(14)
-    end
+      let(:full_path_to_failed_file) { "c:/opscode/chef#{path_to_failed_file}" }
+
+      it "reports the error was not located within cookbooks" do
+        expect(inspector.found_error_in_cookbooks?).to be(true)
+      end
+
+      it "finds the culprit recipe name from the stacktrace" do
+        expect(inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
+      end
 
-    it "prints a pretty message" do
-      @description.display(@outputter)
+      it "finds the line number of the error from the stack trace" do
+        expect(inspector.culprit_line).to eq(14)
+      end
+
+      it "prints a pretty message" do
+        description.display(outputter)
+      end
     end
+
   end
 
 end
diff --git a/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb b/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
index a42d234..5594d6e 100644
--- a/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
+++ b/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
@@ -126,6 +126,13 @@ describe Chef::Formatters::ErrorInspectors::ResourceFailureInspector do
         expect(@inspector.recipe_snippet).to match(/^# In C:\/Users\/btm/)
       end
 
+      it "parses a Windows path" do
+        source_line = "C:\\Windows\\Temp\\packer\\cookbooks\\fake_file.rb:2: undefined local variable or method `non_existent' for main:Object (NameError)"
+        @resource.source_line = source_line
+        @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception)
+        expect(@inspector.recipe_snippet).to match(/^# In C:\\Windows\\Temp\\packer\\/)
+      end
+
       it "parses a unix path" do
         source_line = "/home/btm/src/chef/chef/spec/unit/fake_file.rb:2: undefined local variable or method `non_existent' for main:Object (NameError)"
         @resource.source_line = source_line
diff --git a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
index 4cf3ba8..acf1b15 100644
--- a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
+++ b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
@@ -24,6 +24,7 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do
 
     node.default["kernel"] = Hash.new
     node.default["kernel"][:machine] = :x86_64.to_s
+    node.automatic[:os] = 'windows'
     node
   end
 
@@ -83,6 +84,14 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do
       expect(guard_interpreter.evaluate).to eq(true)
     end
 
+    it "does not corrupt the run_context of the node" do
+      node_run_context_before_guard_execution = parent_resource.run_context
+      expect(node_run_context_before_guard_execution.object_id).to eq(parent_resource.node.run_context.object_id)
+      guard_interpreter.evaluate
+      node_run_context_after_guard_execution = parent_resource.run_context
+      expect(node_run_context_after_guard_execution.object_id).to eq(parent_resource.node.run_context.object_id)
+    end
+
     describe "script command opts switch" do
       let(:command_opts) { {} }
       let(:guard_interpreter) { Chef::GuardInterpreter::ResourceGuardInterpreter.new(parent_resource, "exit 0", command_opts) }
@@ -144,4 +153,3 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do
     end
   end
 end
-
diff --git a/spec/unit/http/authenticator_spec.rb b/spec/unit/http/authenticator_spec.rb
new file mode 100644
index 0000000..48bbdcf
--- /dev/null
+++ b/spec/unit/http/authenticator_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/http/authenticator'
+
+describe Chef::HTTP::Authenticator do
+  let(:class_instance) { Chef::HTTP::Authenticator.new }
+  let(:method) { double("method") }
+  let(:url) { double("url") }
+  let(:headers) { Hash.new }
+  let(:data) { double("data") }
+
+  before do
+    allow(class_instance).to receive(:authentication_headers).and_return({})
+  end
+
+  context "when handle_request is called" do
+    shared_examples_for "merging the server API version into the headers" do
+      it "merges the default version of X-Ops-Server-API-Version into the headers" do
+        # headers returned
+        expect(class_instance.handle_request(method, url, headers, data)[2]).
+          to include({'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION})
+      end
+
+      context "when api_version is set to something other than the default" do
+        let(:class_instance) { Chef::HTTP::Authenticator.new({:api_version => '-10'}) }
+
+        it "merges the requested version of X-Ops-Server-API-Version into the headers" do
+          expect(class_instance.handle_request(method, url, headers, data)[2]).
+            to include({'X-Ops-Server-API-Version' => '-10'})
+        end
+      end
+    end
+
+    context "when !sign_requests?" do
+      before do
+        allow(class_instance).to receive(:sign_requests?).and_return(false)
+      end
+
+      it_behaves_like "merging the server API version into the headers"
+
+      it "authentication_headers is not called" do
+        expect(class_instance).to_not receive(:authentication_headers)
+        class_instance.handle_request(method, url, headers, data)
+      end
+
+    end
+
+    context "when sign_requests?" do
+      before do
+        allow(class_instance).to receive(:sign_requests?).and_return(true)
+      end
+
+      it_behaves_like "merging the server API version into the headers"
+
+      it "calls authentication_headers with the proper input" do
+        expect(class_instance).to receive(:authentication_headers).with(method, url, data).and_return({})
+        class_instance.handle_request(method, url, headers, data)
+      end
+    end
+  end
+end
diff --git a/spec/unit/http/basic_client_spec.rb b/spec/unit/http/basic_client_spec.rb
index 32b32a5..b7552f5 100644
--- a/spec/unit/http/basic_client_spec.rb
+++ b/spec/unit/http/basic_client_spec.rb
@@ -109,5 +109,21 @@ describe "HTTP Connection" do
       end
 
     end
+
+    context "when an empty proxy is set by the environment" do
+      let(:env) do
+        {
+          "https_proxy" => ""
+        }
+      end
+
+      before do
+        allow(subject).to receive(:env).and_return(env)
+      end
+
+      it "to not fail with URI parse exception" do
+        expect { subject.proxy_uri }.to_not raise_error
+      end
+    end
   end
 end
diff --git a/spec/unit/http_spec.rb b/spec/unit/http_spec.rb
index 4d851df..ebb96d2 100644
--- a/spec/unit/http_spec.rb
+++ b/spec/unit/http_spec.rb
@@ -89,4 +89,106 @@ describe Chef::HTTP do
 
   end # head
 
+  describe "retrying connection errors" do
+
+    let(:uri) { "https://chef.example/organizations/default/" }
+
+    subject(:http) { Chef::HTTP.new(uri) }
+
+    # http#http_client gets stubbed later, so eager create
+    let!(:low_level_client) { http.http_client(URI(uri)) }
+
+    let(:http_ok_response) do
+      Net::HTTPOK.new("1.1", 200, "OK").tap do |r|
+        allow(r).to receive(:read_body).and_return("")
+      end
+    end
+
+    before do
+      allow(http).to receive(:http_client).with(URI(uri)).and_return(low_level_client)
+    end
+
+    shared_examples_for "retriable_request_errors" do
+
+      before do
+        expect(low_level_client).to receive(:request).exactly(5).times.and_raise(exception)
+        expect(http).to receive(:sleep).exactly(5).times.and_return(1)
+        expect(low_level_client).to receive(:request).and_return([low_level_client, http_ok_response])
+      end
+
+      it "retries the request 5 times" do
+        http.get('/')
+      end
+
+    end
+
+    shared_examples_for "errors_that_are_not_retried" do
+
+      before do
+        expect(low_level_client).to receive(:request).exactly(1).times.and_raise(exception)
+        expect(http).to_not receive(:sleep)
+      end
+
+      it "raises the error without retrying or sleeping" do
+        # We modify the strings to give addtional context, but the exception class should be the same
+        expect { http.get("/") }.to raise_error(exception.class)
+      end
+    end
+
+    context "when ECONNRESET is raised" do
+
+      let(:exception) { Errno::ECONNRESET.new("example error") }
+
+      include_examples "retriable_request_errors"
+
+    end
+
+    context "when SocketError is raised" do
+
+      let(:exception) { SocketError.new("example error") }
+
+      include_examples "retriable_request_errors"
+
+    end
+
+    context "when ETIMEDOUT is raised" do
+
+      let(:exception) { Errno::ETIMEDOUT.new("example error") }
+
+      include_examples "retriable_request_errors"
+
+    end
+
+    context "when ECONNREFUSED is raised" do
+
+      let(:exception) { Errno::ECONNREFUSED.new("example error") }
+
+      include_examples "retriable_request_errors"
+
+    end
+
+    context "when Timeout::Error is raised" do
+
+      let(:exception) { Timeout::Error.new("example error") }
+
+      include_examples "retriable_request_errors"
+
+    end
+
+    context "when OpenSSL::SSL::SSLError is raised" do
+
+      let(:exception) { OpenSSL::SSL::SSLError.new("example error") }
+
+      include_examples "retriable_request_errors"
+
+    end
+
+    context "when OpenSSL::SSL::SSLError is raised for certificate validation failure" do
+
+      let(:exception) { OpenSSL::SSL::SSLError.new("ssl_connect returned=1 errno=0 state=sslv3 read server certificate b: certificate verify failed") }
+
+      include_examples "errors_that_are_not_retried"
+
+    end
+  end
 end
diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb
index 7482ba8..fd6469c 100644
--- a/spec/unit/json_compat_spec.rb
+++ b/spec/unit/json_compat_spec.rb
@@ -67,13 +67,11 @@ describe Chef::JSONCompat do
       expect(Chef::JSONCompat.to_json_pretty(f)).to eql("{\n  \"foo\": 1234,\n  \"bar\": {\n    \"baz\": 5678\n  }\n}\n")
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) { Foo.new }
     end
   end
 
-  # On FreeBSD 10.1 i386 rspec fails with a SystemStackError loading the expect line with more that 252 entries
-  # https://github.com/chef/chef/issues/3101
   describe "with the file with 252 or less nested entries" do
     let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'nested.json')) }
     let(:hash) { Chef::JSONCompat.from_json(json) }
@@ -84,7 +82,10 @@ describe Chef::JSONCompat do
       end
 
       it "should has 'test' as a 252 nested value" do
-        expect(hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['ke [...]
+        v = 252.times.inject(hash) do |memo, _|
+          memo['key']
+        end
+        expect(v).to eq('test')
       end
     end
   end
diff --git a/spec/unit/key_spec.rb b/spec/unit/key_spec.rb
new file mode 100644
index 0000000..94ebbf6
--- /dev/null
+++ b/spec/unit/key_spec.rb
@@ -0,0 +1,634 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+require 'chef/key'
+
+describe Chef::Key do
+  # whether user or client irrelevent to these tests
+  let(:key) { Chef::Key.new("original_actor", "user") }
+  let(:public_key_string) do
+     <<EOS
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
+KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
+WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
+E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
+IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
+Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
+0wIDAQAB
+-----END PUBLIC KEY-----
+EOS
+  end
+
+  shared_examples_for "fields with username type validation" do
+    context "when invalid input is passed" do
+      # It is not feasible to check all invalid characters.  Here are a few
+      # that we probably care about.
+      it "should raise an ArgumentError" do
+        # capital letters
+        expect { key.send(field, "Bar") }.to raise_error(ArgumentError)
+        # slashes
+        expect { key.send(field, "foo/bar") }.to raise_error(ArgumentError)
+        # ?
+        expect { key.send(field, "foo?") }.to raise_error(ArgumentError)
+        # &
+        expect { key.send(field, "foo&") }.to raise_error(ArgumentError)
+        # spaces
+        expect { key.send(field, "foo ") }.to raise_error(ArgumentError)
+      end
+    end
+  end
+
+  shared_examples_for "string fields that are settable" do
+    context "when it is set with valid input" do
+      it "should set the field" do
+        key.send(field, valid_input)
+        expect(key.send(field)).to eq(valid_input)
+      end
+    end
+
+    context "when you feed it anything but a string" do
+      it "should raise an ArgumentError" do
+        expect { key.send(field, Hash.new) }.to raise_error(ArgumentError)
+      end
+    end
+  end
+
+
+  describe "when a new Chef::Key object is initialized with invalid input" do
+    it "should raise an InvalidKeyArgument" do
+      expect { Chef::Key.new("original_actor", "not_a_user_or_client") }.to raise_error(Chef::Exceptions::InvalidKeyArgument)
+    end
+  end
+
+  describe "when a new Chef::Key object is initialized with valid input" do
+    it "should be a Chef::Key" do
+      expect(key).to be_a_kind_of(Chef::Key)
+    end
+
+    it "should properly set the actor" do
+      expect(key.actor).to eq("original_actor")
+    end
+  end
+
+  describe "when actor field is set" do
+    it_should_behave_like "string fields that are settable" do
+      let(:field) { :actor }
+      let(:valid_input) { "new_field_value" }
+    end
+
+    it_should_behave_like "fields with username type validation" do
+      let(:field) { :actor }
+    end
+  end
+
+  describe "when the name field is set" do
+    it_should_behave_like "string fields that are settable" do
+      let(:field) { :name }
+      let(:valid_input) { "new_field_value" }
+    end
+  end
+
+  describe "when the private_key field is set" do
+    it_should_behave_like "string fields that are settable" do
+      let(:field) { :private_key }
+      let(:valid_input) { "new_field_value" }
+    end
+  end
+
+  describe "when the public_key field is set" do
+    it_should_behave_like "string fields that are settable" do
+      let(:field) { :public_key }
+      let(:valid_input) { "new_field_value" }
+    end
+
+    context "when create_key is true" do
+      before do
+        key.create_key true
+      end
+
+      it "should raise an InvalidKeyAttribute" do
+        expect { key.public_key public_key_string }.to raise_error(Chef::Exceptions::InvalidKeyAttribute)
+      end
+    end
+  end
+
+  describe "when the create_key field is set" do
+    context "when it is set to true" do
+      it "should set the field" do
+        key.create_key(true)
+        expect(key.create_key).to eq(true)
+      end
+    end
+
+    context "when it is set to false" do
+      it "should set the field" do
+        key.create_key(false)
+        expect(key.create_key).to eq(false)
+      end
+    end
+
+    context "when anything but a TrueClass or FalseClass is passed" do
+      it "should raise an ArgumentError" do
+        expect { key.create_key "not_a_boolean" }.to raise_error(ArgumentError)
+      end
+    end
+
+    context "when public_key is defined" do
+      before do
+        key.public_key public_key_string
+      end
+
+      it "should raise an InvalidKeyAttribute" do
+        expect { key.create_key true }.to raise_error(Chef::Exceptions::InvalidKeyAttribute)
+      end
+    end
+  end
+
+  describe "when the expiration_date field is set" do
+    context "when a valid date is passed" do
+      it_should_behave_like "string fields that are settable" do
+        let(:field) { :public_key }
+        let(:valid_input) { "2020-12-24T21:00:00Z" }
+      end
+    end
+
+    context "when infinity is passed" do
+      it_should_behave_like "string fields that are settable" do
+        let(:field) { :public_key }
+        let(:valid_input) { "infinity" }
+      end
+    end
+
+    context "when an invalid date is passed" do
+      it "should raise an ArgumentError" do
+        expect { key.expiration_date "invalid_date" }.to raise_error(ArgumentError)
+        # wrong years
+        expect { key.expiration_date "20-12-24T21:00:00Z" }.to raise_error(ArgumentError)
+      end
+
+      context "when it is a valid UTC date missing a Z" do
+        it "should raise an ArgumentError" do
+          expect { key.expiration_date "2020-12-24T21:00:00" }.to raise_error(ArgumentError)
+        end
+      end
+    end
+  end # when the expiration_date field is set
+
+  describe "when serializing to JSON" do
+    shared_examples_for "common json operations" do
+      it "should serializes as a JSON object" do
+        expect(json).to match(/^\{.+\}$/)
+      end
+
+      it "should include the actor value under the key relative to the actor_field_name passed" do
+        expect(json).to include(%Q("#{new_key.actor_field_name}":"original_actor"))
+      end
+
+      it "should include the name field when present" do
+        new_key.name("monkeypants")
+        expect(new_key.to_json).to include(%q{"name":"monkeypants"})
+      end
+
+      it "should not include the name if not present" do
+        expect(json).to_not include("name")
+      end
+
+      it "should include the public_key field when present" do
+        new_key.public_key "this_public_key"
+        expect(new_key.to_json).to include(%q("public_key":"this_public_key"))
+      end
+
+      it "should not include the public_key if not present" do
+        expect(json).to_not include("public_key")
+      end
+
+      it "should include the private_key field when present" do
+        new_key.private_key "this_public_key"
+        expect(new_key.to_json).to include(%q("private_key":"this_public_key"))
+      end
+
+      it "should not include the private_key if not present" do
+        expect(json).to_not include("private_key")
+      end
+
+      it "should include the expiration_date field when present" do
+        new_key.expiration_date "2020-12-24T21:00:00Z"
+        expect(new_key.to_json).to include(%Q("expiration_date":"2020-12-24T21:00:00Z"))
+      end
+
+      it "should not include the expiration_date if not present" do
+        expect(json).to_not include("expiration_date")
+      end
+
+      it "should include the create_key field when present" do
+        new_key.create_key true
+        expect(new_key.to_json).to include(%q("create_key":true))
+      end
+
+      it "should not include the create_key if not present" do
+        expect(json).to_not include("create_key")
+      end
+    end
+
+    context "when key is for a user" do
+      it_should_behave_like "common json operations" do
+        let(:new_key) { Chef::Key.new("original_actor", "user") }
+        let(:json) do
+          new_key.to_json
+        end
+      end
+    end
+
+    context "when key is for a client" do
+      it_should_behave_like "common json operations" do
+        let(:new_key) { Chef::Key.new("original_actor", "client") }
+        let(:json) do
+          new_key.to_json
+        end
+      end
+    end
+
+  end # when serializing to JSON
+
+  describe "when deserializing from JSON" do
+    shared_examples_for "a deserializable object" do
+      it "deserializes to a Chef::Key object" do
+        expect(key).to be_a_kind_of(Chef::Key)
+      end
+
+      it "preserves the actor" do
+        expect(key.actor).to eq("turtle")
+      end
+
+      it "preserves the name" do
+        expect(key.name).to eq("key_name")
+      end
+
+      it "includes the public key if present" do
+        expect(key.public_key).to eq(public_key_string)
+      end
+
+      it "includes the expiration_date if present" do
+        expect(key.expiration_date).to eq("infinity")
+      end
+
+      it "includes the private_key if present" do
+        expect(key.private_key).to eq("some_private_key")
+      end
+
+      it "includes the create_key if present" do
+        expect(key_with_create_key_field.create_key).to eq(true)
+      end
+    end
+
+    context "when deserializing a key for a user" do
+      it_should_behave_like "a deserializable object" do
+        let(:key) do
+          o = { "user" => "turtle",
+                "name" => "key_name",
+                "public_key" => public_key_string,
+                "private_key" => "some_private_key",
+                "expiration_date" => "infinity"}
+          Chef::Key.from_json(o.to_json)
+        end
+        let(:key_with_create_key_field) do
+          o = { "user" => "turtle",
+                "create_key" => true }
+          Chef::Key.from_json(o.to_json)
+        end
+      end
+    end
+
+    context "when deserializing a key for a client" do
+      it_should_behave_like "a deserializable object" do
+        let(:key) do
+          o = { "client" => "turtle",
+                "name" => "key_name",
+                "public_key" => public_key_string,
+                "private_key" => "some_private_key",
+                "expiration_date" => "infinity"}
+          Chef::Key.from_json(o.to_json)
+        end
+        let(:key_with_create_key_field) do
+          o = { "client" => "turtle",
+                "create_key" => true }
+          Chef::Key.from_json(o.to_json)
+        end
+      end
+    end
+  end # when deserializing from JSON
+
+
+  describe "API Interactions" do
+    let(:rest) do
+      Chef::Config[:chef_server_root] = "http://www.example.com"
+      Chef::Config[:chef_server_url] = "http://www.example.com/organizations/test_org"
+      r = double('rest')
+      allow(Chef::REST).to receive(:new).and_return(r)
+      r
+    end
+
+    let(:user_key) do
+      o = Chef::Key.new("foobar", "user")
+      o
+    end
+
+    let(:client_key) do
+      o = Chef::Key.new("foobar", "client")
+      o
+    end
+
+    describe "list" do
+      context "when listing keys for a user" do
+        let(:response) { [{"uri" => "http://www.example.com/users/keys/foobar", "name"=>"foobar", "expired"=>false}] }
+        let(:inflated_response) { {"foobar" => user_key} }
+
+        it "lists all keys" do
+          expect(rest).to receive(:get_rest).with("users/#{user_key.actor}/keys").and_return(response)
+          expect(Chef::Key.list_by_user("foobar")).to eq(response)
+        end
+
+        it "inflate all keys" do
+          allow(Chef::Key).to receive(:load_by_user).with(user_key.actor, "foobar").and_return(user_key)
+          expect(rest).to receive(:get_rest).with("users/#{user_key.actor}/keys").and_return(response)
+          expect(Chef::Key.list_by_user("foobar", true)).to eq(inflated_response)
+        end
+
+      end
+
+      context "when listing keys for a client" do
+        let(:response) { [{"uri" => "http://www.example.com/users/keys/foobar", "name"=>"foobar", "expired"=>false}] }
+        let(:inflated_response) { {"foobar" => client_key} }
+
+        it "lists all keys" do
+          expect(rest).to receive(:get_rest).with("clients/#{client_key.actor}/keys").and_return(response)
+          expect(Chef::Key.list_by_client("foobar")).to eq(response)
+        end
+
+        it "inflate all keys" do
+          allow(Chef::Key).to receive(:load_by_client).with(client_key.actor, "foobar").and_return(client_key)
+          expect(rest).to receive(:get_rest).with("clients/#{user_key.actor}/keys").and_return(response)
+          expect(Chef::Key.list_by_client("foobar", true)).to eq(inflated_response)
+        end
+
+      end
+    end
+
+
+    describe "create" do
+      shared_examples_for "create key" do
+        context "when a field is missing" do
+          it "should raise a MissingKeyAttribute" do
+            expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+          end
+        end
+
+        context "when the name field is missing" do
+          before do
+            key.public_key public_key_string
+            key.expiration_date "2020-12-24T21:00:00Z"
+          end
+
+          it "creates a new key via the API with the fingerprint as the name" do
+            expect(rest).to receive(:post_rest).with(url,
+                                                     {"name" => "12:3e:33:73:0b:f4:ec:72:dc:f0:4c:51:62:27:08:76:96:24:f4:4a",
+                                                      "public_key" => key.public_key,
+                                                      "expiration_date" => key.expiration_date}).and_return({})
+            key.create
+          end
+        end
+
+        context "when every field is populated" do
+          before do
+            key.name "key_name"
+            key.public_key public_key_string
+            key.expiration_date "2020-12-24T21:00:00Z"
+            key.create_key false
+          end
+
+          context "when create_key is false" do
+            it "creates a new key via the API" do
+              expect(rest).to receive(:post_rest).with(url,
+                                                       {"name" => key.name,
+                                                        "public_key" => key.public_key,
+                                                        "expiration_date" => key.expiration_date}).and_return({})
+              key.create
+            end
+          end
+
+          context "when create_key is true and public_key is nil" do
+
+            before do
+              key.delete_public_key
+              key.create_key true
+              $expected_output = {
+                actor_type => "foobar",
+                "name" => key.name,
+                "create_key" => true,
+                "expiration_date" => key.expiration_date
+              }
+              $expected_input = {
+                "name" => key.name,
+                "create_key" => true,
+                "expiration_date" => key.expiration_date
+              }
+            end
+
+            it "should create a new key via the API" do
+              expect(rest).to receive(:post_rest).with(url, $expected_input).and_return({})
+              key.create
+            end
+
+            context "when the server returns the private_key via key.create" do
+              before do
+                allow(rest).to receive(:post_rest).with(url, $expected_input).and_return({"private_key" => "this_private_key"})
+              end
+
+              it "key.create returns the original key plus the private_key" do
+                expect(key.create.to_hash).to eq($expected_output.merge({"private_key" => "this_private_key"}))
+              end
+            end
+          end
+
+          context "when create_key is false and public_key is nil" do
+            before do
+              key.delete_public_key
+              key.create_key false
+            end
+            it "should raise an InvalidKeyArgument" do
+              expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+            end
+          end
+        end
+      end
+
+      context "when creating a user key" do
+        it_should_behave_like "create key" do
+          let(:url) { "users/#{key.actor}/keys" }
+          let(:key) { user_key }
+          let(:actor_type) { "user" }
+        end
+      end
+
+      context "when creating a client key" do
+        it_should_behave_like "create key" do
+          let(:url) { "clients/#{client_key.actor}/keys" }
+          let(:key) { client_key }
+          let(:actor_type) { "client" }
+        end
+      end
+    end # create
+
+    describe "update" do
+      shared_examples_for "update key" do
+        context "when name is missing and no argument was passed to update" do
+          it "should raise an MissingKeyAttribute" do
+            expect { key.update }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+          end
+        end
+
+        context "when some fields are populated" do
+          before do
+            key.name "key_name"
+            key.expiration_date "2020-12-24T21:00:00Z"
+          end
+
+          it "should update the key via the API" do
+            expect(rest).to receive(:put_rest).with(url, key.to_hash).and_return({})
+            key.update
+          end
+        end
+
+        context "when @name is not nil and a arg is passed to update" do
+          before do
+            key.name "new_name"
+          end
+
+          it "passes @name in the body and the arg in the PUT URL" do
+            expect(rest).to receive(:put_rest).with(update_name_url, key.to_hash).and_return({})
+            key.update("old_name")
+          end
+        end
+
+        context "when the server returns a public_key and create_key is true" do
+          before do
+            key.name "key_name"
+            key.create_key true
+            allow(rest).to receive(:put_rest).with(url, key.to_hash).and_return({
+                                                                                  "key" => "key_name",
+                                                                                  "public_key" => public_key_string
+                                                                                })
+
+          end
+
+          it "returns a key with public_key populated" do
+            new_key = key.update
+            expect(new_key.public_key).to eq(public_key_string)
+          end
+
+          it "returns a key without create_key set" do
+            new_key = key.update
+            expect(new_key.create_key).to be_nil
+          end
+        end
+      end
+
+      context "when updating a user key" do
+        it_should_behave_like "update key" do
+          let(:url) { "users/#{key.actor}/keys/#{key.name}" }
+          let(:update_name_url) { "users/#{key.actor}/keys/old_name" }
+          let(:key) { user_key }
+        end
+      end
+
+      context "when updating a client key" do
+        it_should_behave_like "update key" do
+          let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" }
+          let(:update_name_url) { "clients/#{client_key.actor}/keys/old_name" }
+          let(:key) { client_key }
+        end
+      end
+
+    end #update
+
+    describe "load" do
+      shared_examples_for "load" do
+        it "should load a named key from the API" do
+          expect(rest).to receive(:get_rest).with(url).and_return({"user" => "foobar", "name" => "test_key_name", "public_key" => public_key_string, "expiration_date" => "infinity"})
+          key = Chef::Key.send(load_method, "foobar", "test_key_name")
+          expect(key.actor).to eq("foobar")
+          expect(key.name).to eq("test_key_name")
+          expect(key.public_key).to eq(public_key_string)
+          expect(key.expiration_date).to eq("infinity")
+        end
+      end
+
+      describe "load_by_user" do
+        it_should_behave_like "load" do
+          let(:load_method) { :load_by_user }
+          let(:url) { "users/foobar/keys/test_key_name" }
+        end
+      end
+
+      describe "load_by_client" do
+        it_should_behave_like "load" do
+          let(:load_method) { :load_by_client }
+          let(:url) { "clients/foobar/keys/test_key_name" }
+        end
+      end
+
+    end #load
+
+    describe "destroy" do
+      shared_examples_for "destroy key" do
+        context "when name is missing" do
+          it "should raise an MissingKeyAttribute" do
+            expect { Chef::Key.new("username", "user").destroy }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+          end
+        end
+
+        before do
+          key.name "key_name"
+        end
+        context "when name is not missing" do
+          it "should delete the key via the API" do
+            expect(rest).to receive(:delete_rest).with(url).and_return({})
+            key.destroy
+          end
+        end
+      end
+
+      context "when destroying a user key" do
+        it_should_behave_like "destroy key" do
+          let(:url) { "users/#{key.actor}/keys/#{key.name}" }
+          let(:key) { user_key }
+        end
+      end
+
+      context "when destroying a client key" do
+        it_should_behave_like "destroy key" do
+          let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" }
+          let(:key) { client_key }
+        end
+      end
+    end
+  end # API Interactions
+end
diff --git a/spec/unit/knife/bootstrap/client_builder_spec.rb b/spec/unit/knife/bootstrap/client_builder_spec.rb
index e6aa307..e7232fe 100644
--- a/spec/unit/knife/bootstrap/client_builder_spec.rb
+++ b/spec/unit/knife/bootstrap/client_builder_spec.rb
@@ -149,6 +149,22 @@ describe Chef::Knife::Bootstrap::ClientBuilder do
       client_builder.run
     end
 
+    it "does not add tags by default" do
+      allow(node).to receive(:run_list).with([])
+      expect(node).to_not receive(:tags)
+      client_builder.run
+    end
+
+    it "adds tags to the node when given" do
+      tag_receiver = []
+
+      knife_config[:tags] = %w[foo bar]
+      allow(node).to receive(:run_list).with([])
+      allow(node).to receive(:tags).and_return(tag_receiver)
+      client_builder.run
+      expect(tag_receiver).to eq %w[foo bar]
+    end
+
     it "builds a node when the run_list is a string" do
       knife_config[:run_list] = "role[base],role[app]"
       expect(node).to receive(:run_list).with(["role[base]", "role[app]"])
@@ -174,5 +190,16 @@ describe Chef::Knife::Bootstrap::ClientBuilder do
       expect(node).to receive(:run_list).with([])
       client_builder.run
     end
+
+    it "builds a node with policy_name and policy_group when given" do
+      knife_config[:policy_name] = "my-app"
+      knife_config[:policy_group] = "staging"
+
+      expect(node).to receive(:run_list).with([])
+      expect(node).to receive(:policy_name=).with("my-app")
+      expect(node).to receive(:policy_group=).with("staging")
+
+      client_builder.run
+    end
   end
 end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
index f1ca510..19146ea 100644
--- a/spec/unit/knife/bootstrap_spec.rb
+++ b/spec/unit/knife/bootstrap_spec.rb
@@ -23,7 +23,7 @@ require 'net/ssh'
 
 describe Chef::Knife::Bootstrap do
   before do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
   end
   let(:knife) do
     Chef::Log.logger = Logger.new(StringIO.new)
@@ -235,12 +235,39 @@ describe Chef::Knife::Bootstrap do
       expect(knife.render_template).to eq('{"run_list":["role[base]","recipe[cupcakes]"]}')
     end
 
-    it "should have foo => {bar => baz} in the first_boot" do
-      knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
-      knife.merge_configs
-      expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
-      actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
-      expect(actual_hash).to eq(expected_hash)
+    context "with bootstrap_attribute options" do
+      let(:jsonfile) {
+        file = Tempfile.new (['node', '.json'])
+        File.open(file.path, "w") {|f| f.puts '{"foo":{"bar":"baz"}}' }
+        file
+      }
+
+      it "should have foo => {bar => baz} in the first_boot from cli" do
+        knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
+        knife.merge_configs
+        expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
+        actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
+        expect(actual_hash).to eq(expected_hash)
+      end
+
+      it "should have foo => {bar => baz} in the first_boot from file" do
+        knife.parse_options(["--json-attribute-file", jsonfile.path])
+        knife.merge_configs
+        expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
+        actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
+        expect(actual_hash).to eq(expected_hash)
+        jsonfile.close
+      end
+
+      context "when --json-attributes and --json-attribute-file were both passed" do
+        it "raises a Chef::Exceptions::BootstrapCommandInputError with the proper error message" do
+          knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
+          knife.parse_options(["--json-attribute-file", jsonfile.path])
+          knife.merge_configs
+          expect{ knife.run }.to raise_error(Chef::Exceptions::BootstrapCommandInputError)
+          jsonfile.close
+        end
+      end
     end
   end
 
@@ -250,14 +277,14 @@ describe Chef::Knife::Bootstrap do
     it "should create a hint file when told to" do
       knife.parse_options(["--hint", "openstack"])
       knife.merge_configs
-      expect(knife.render_template).to match /\/etc\/chef\/ohai\/hints\/openstack.json/
+      expect(knife.render_template).to match(/\/etc\/chef\/ohai\/hints\/openstack.json/)
     end
 
     it "should populate a hint file with JSON when given a file to read" do
       allow(::File).to receive(:read).and_return('{ "foo" : "bar" }')
       knife.parse_options(["--hint", "openstack=hints/openstack.json"])
       knife.merge_configs
-      expect(knife.render_template).to match /\{\"foo\":\"bar\"\}/
+      expect(knife.render_template).to match(/\{\"foo\":\"bar\"\}/)
     end
   end
 
@@ -395,6 +422,73 @@ describe Chef::Knife::Bootstrap do
     end
   end
 
+  describe "handling policyfile options" do
+
+    context "when only policy_name is given" do
+
+      let(:bootstrap_cli_options) { %w[ --policy-name my-app-server ] }
+
+      it "returns an error stating that policy_name and policy_group must be given together" do
+        expect { knife.validate_options! }.to raise_error(SystemExit)
+        expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
+      end
+
+    end
+
+    context "when only policy_group is given" do
+
+      let(:bootstrap_cli_options) { %w[ --policy-group staging ] }
+
+      it "returns an error stating that policy_name and policy_group must be given together" do
+        expect { knife.validate_options! }.to raise_error(SystemExit)
+        expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
+      end
+
+    end
+
+    context "when both policy_name and policy_group are given, but run list is also given" do
+
+      let(:bootstrap_cli_options) { %w[ --policy-name my-app --policy-group staging --run-list cookbook ] }
+
+      it "returns an error stating that policyfile and run_list are exclusive" do
+        expect { knife.validate_options! }.to raise_error(SystemExit)
+        expect(stderr.string).to include("ERROR: Policyfile options and --run-list are exclusive")
+      end
+
+    end
+
+    context "when policy_name and policy_group are given with no conflicting options" do
+
+      let(:bootstrap_cli_options) { %w[ --policy-name my-app --policy-group staging ] }
+
+      it "passes options validation" do
+        expect { knife.validate_options! }.to_not raise_error
+      end
+
+      it "passes them into the bootstrap context" do
+        expect(knife.bootstrap_context.first_boot).to have_key(:policy_name)
+        expect(knife.bootstrap_context.first_boot).to have_key(:policy_group)
+      end
+
+    end
+
+    # https://github.com/chef/chef/issues/4131
+    # Arguably a bug in the plugin: it shouldn't be setting this to nil, but it
+    # worked before, so make it work now.
+    context "when a plugin sets the run list option to nil" do
+
+      before do
+        knife.config[:run_list] = nil
+      end
+
+      it "passes options validation" do
+        expect { knife.validate_options! }.to_not raise_error
+      end
+
+    end
+
+  end
+
   describe "when configuring the underlying knife ssh command" do
     context "from the command line" do
       let(:knife_ssh) do
@@ -405,7 +499,7 @@ describe Chef::Knife::Bootstrap do
         Chef::Config[:knife][:ssh_user] = nil
         Chef::Config[:knife][:ssh_port] = nil
         knife.config[:forward_agent] = true
-        knife.config[:identity_file] = "~/.ssh/me.rsa"
+        knife.config[:ssh_identity_file] = "~/.ssh/me.rsa"
         allow(knife).to receive(:render_template).and_return("")
         knife.knife_ssh
       end
@@ -431,7 +525,7 @@ describe Chef::Knife::Bootstrap do
       end
 
       it "configures the ssh identity file" do
-        expect(knife_ssh.config[:identity_file]).to eq('~/.ssh/me.rsa')
+        expect(knife_ssh.config[:ssh_identity_file]).to eq('~/.ssh/me.rsa')
       end
     end
 
@@ -466,7 +560,7 @@ describe Chef::Knife::Bootstrap do
         Chef::Config[:knife][:ssh_user] = "curiosity"
         Chef::Config[:knife][:ssh_port] = "2430"
         Chef::Config[:knife][:forward_agent] = true
-        Chef::Config[:knife][:identity_file] = "~/.ssh/you.rsa"
+        Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/you.rsa"
         Chef::Config[:knife][:ssh_gateway] = "towel.blinkenlights.nl"
         Chef::Config[:knife][:host_key_verify] = true
         allow(knife).to receive(:render_template).and_return("")
@@ -488,7 +582,7 @@ describe Chef::Knife::Bootstrap do
       end
 
       it "configures the ssh identity file" do
-        expect(knife_ssh.config[:identity_file]).to eq('~/.ssh/you.rsa')
+        expect(knife_ssh.config[:ssh_identity_file]).to eq('~/.ssh/you.rsa')
       end
 
       it "configures the ssh gateway" do
@@ -504,7 +598,7 @@ describe Chef::Knife::Bootstrap do
       let(:knife_ssh_with_password_auth) do
         knife.name_args = ["foo.example.com"]
         knife.config[:ssh_user]      = "rooty"
-        knife.config[:identity_file] = "~/.ssh/me.rsa"
+        knife.config[:ssh_identity_file] = "~/.ssh/me.rsa"
         allow(knife).to receive(:render_template).and_return("")
         k = knife.knife_ssh
         allow(k).to receive(:get_password).and_return('typed_in_password')
@@ -517,7 +611,7 @@ describe Chef::Knife::Bootstrap do
       end
 
       it "configures knife not to use the identity file that didn't work previously" do
-        expect(knife_ssh_with_password_auth.config[:identity_file]).to be_nil
+        expect(knife_ssh_with_password_auth.config[:ssh_identity_file]).to be_nil
       end
     end
   end
@@ -525,14 +619,15 @@ describe Chef::Knife::Bootstrap do
   it "verifies that a server to bootstrap was given as a command line arg" do
     knife.name_args = nil
     expect { knife.run }.to raise_error(SystemExit)
-    expect(stderr.string).to match /ERROR:.+FQDN or ip/
+    expect(stderr.string).to match(/ERROR:.+FQDN or ip/)
   end
 
   describe "when running the bootstrap" do
     let(:knife_ssh) do
       knife.name_args = ["foo.example.com"]
+      knife.config[:chef_node_name] = "foo.example.com"
       knife.config[:ssh_user]      = "rooty"
-      knife.config[:identity_file] = "~/.ssh/me.rsa"
+      knife.config[:ssh_identity_file] = "~/.ssh/me.rsa"
       allow(knife).to receive(:render_template).and_return("")
       knife_ssh = knife.knife_ssh
       allow(knife).to receive(:knife_ssh).and_return(knife_ssh)
@@ -590,6 +685,12 @@ describe Chef::Knife::Bootstrap do
         expect(knife.chef_vault_handler).not_to receive(:run).with(node_name: knife.config[:chef_node_name])
         knife.run
       end
+
+      it "raises an exception if the config[:chef_node_name] is not present" do
+        knife.config[:chef_node_name] = nil
+
+        expect { knife.run }.to raise_error(SystemExit)
+      end
     end
 
     context "when the validation key is not present" do
@@ -604,6 +705,12 @@ describe Chef::Knife::Bootstrap do
         expect(knife.chef_vault_handler).to receive(:run).with(node_name: knife.config[:chef_node_name])
         knife.run
       end
+
+      it "raises an exception if the config[:chef_node_name] is not present" do
+        knife.config[:chef_node_name] = nil
+
+        expect { knife.run }.to raise_error(SystemExit)
+      end
     end
 
     context "when the validation_key is nil" do
diff --git a/spec/unit/knife/client_bulk_delete_spec.rb b/spec/unit/knife/client_bulk_delete_spec.rb
index 45bb4dd..1a6317a 100644
--- a/spec/unit/knife/client_bulk_delete_spec.rb
+++ b/spec/unit/knife/client_bulk_delete_spec.rb
@@ -45,7 +45,7 @@ describe Chef::Knife::ClientBulkDelete do
     clients = Hash.new
 
     nonvalidator_client_names.each do |client_name|
-      client = Chef::ApiClient.new()
+      client = Chef::ApiClientV1.new()
       client.name(client_name)
       allow(client).to receive(:destroy).and_return(true)
       clients[client_name] = client
@@ -59,7 +59,7 @@ describe Chef::Knife::ClientBulkDelete do
     clients = Hash.new
 
     validator_client_names.each do |validator_client_name|
-      validator_client = Chef::ApiClient.new()
+      validator_client = Chef::ApiClientV1.new()
       validator_client.name(validator_client_name)
       allow(validator_client).to receive(:validator).and_return(true)
       allow(validator_client).to receive(:destroy).and_return(true)
@@ -75,7 +75,7 @@ describe Chef::Knife::ClientBulkDelete do
   }
 
   before(:each) do
-    allow(Chef::ApiClient).to receive(:list).and_return(clients)
+    allow(Chef::ApiClientV1).to receive(:list).and_return(clients)
   end
 
   describe "run" do
@@ -89,7 +89,7 @@ describe Chef::Knife::ClientBulkDelete do
 
     describe "with any clients" do
       it "should get the list of the clients" do
-        expect(Chef::ApiClient).to receive(:list)
+        expect(Chef::ApiClientV1).to receive(:list)
         knife.run
       end
 
diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb
index 10d386b..a1dcc56 100644
--- a/spec/unit/knife/client_create_spec.rb
+++ b/spec/unit/knife/client_create_spec.rb
@@ -22,6 +22,8 @@ Chef::Knife::ClientCreate.load_deps
 
 describe Chef::Knife::ClientCreate do
   let(:stderr) { StringIO.new }
+  let(:stdout) { StringIO.new }
+
 
   let(:default_client_hash) do
     {
@@ -32,84 +34,153 @@ describe Chef::Knife::ClientCreate do
   end
 
   let(:client) do
-    c = double("Chef::ApiClient")
-    allow(c).to receive(:save).and_return({"private_key" => ""})
-    allow(c).to receive(:to_s).and_return("client[adam]")
-    c
+    Chef::ApiClientV1.new
   end
 
   let(:knife) do
     k = Chef::Knife::ClientCreate.new
-    k.name_args = [ "adam" ]
-    k.ui.config[:disable_editing] = true
+    k.name_args = []
+    allow(k).to receive(:client).and_return(client)
+    allow(k).to receive(:edit_data).with(client).and_return(client)
     allow(k.ui).to receive(:stderr).and_return(stderr)
-    allow(k.ui).to receive(:stdout).and_return(stderr)
+    allow(k.ui).to receive(:stdout).and_return(stdout)
     k
   end
 
+  before do
+    allow(client).to receive(:to_s).and_return("client[adam]")
+    allow(knife).to receive(:create_client).and_return(client)
+  end
+
   before(:each) do
     Chef::Config[:node_name]  = "webmonkey.example.com"
   end
 
   describe "run" do
-    it "should create and save the ApiClient" do
-      expect(Chef::ApiClient).to receive(:from_hash).and_return(client)
-      expect(client).to receive(:save)
-      knife.run
+    context "when nothing is passed" do
+      # from spec/support/shared/unit/knife_shared.rb
+      it_should_behave_like "mandatory field missing" do
+        let(:name_args) { [] }
+        let(:fieldname) { 'client name' }
+      end
     end
 
-    it "should print a message upon creation" do
-      expect(Chef::ApiClient).to receive(:from_hash).and_return(client)
-      expect(client).to receive(:save)
-      knife.run
-      expect(stderr.string).to match /Created client.*adam/i
-    end
+    context "when clientname is passed" do
+      before do
+        knife.name_args = ['adam']
+      end
 
-    it "should set the Client name" do
-      expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("name" => "adam")).and_return(client)
-      knife.run
-    end
+      context "when public_key and prevent_keygen are passed" do
+        before do
+          knife.config[:public_key] = "some_key"
+          knife.config[:prevent_keygen] = true
+        end
+
+        it "prints the usage" do
+          expect(knife).to receive(:show_usage)
+          expect { knife.run }.to raise_error(SystemExit)
+        end
+
+        it "prints a relevant error message" do
+          expect { knife.run }.to raise_error(SystemExit)
+          expect(stderr.string).to match /You cannot pass --public-key and --prevent-keygen/
+        end
+      end
 
-    it "by default it is not an admin" do
-      expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("admin" => false)).and_return(client)
-      knife.run
-    end
+      it "should create the ApiClient" do
+        expect(knife).to receive(:create_client)
+        knife.run
+      end
 
-    it "by default it is not a validator" do
-      expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("validator" => false)).and_return(client)
-      knife.run
-    end
+      it "should print a message upon creation" do
+        expect(knife).to receive(:create_client)
+        knife.run
+        expect(stderr.string).to match /Created client.*adam/i
+      end
 
-    it "should allow you to edit the data" do
-      expect(knife).to receive(:edit_hash).with(default_client_hash).and_return(default_client_hash)
-      allow(Chef::ApiClient).to receive(:from_hash).and_return(client)
-      knife.run
-    end
+      it "should set the Client name" do
+        knife.run
+        expect(client.name).to eq("adam")
+      end
 
-    describe "with -f or --file" do
-      it "should write the private key to a file" do
-        knife.config[:file] = "/tmp/monkeypants"
-        allow_any_instance_of(Chef::ApiClient).to receive(:save).and_return({ 'private_key' => "woot" })
-        filehandle = double("Filehandle")
-        expect(filehandle).to receive(:print).with('woot')
-        expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
+      it "by default it is not an admin" do
         knife.run
+        expect(client.admin).to be_falsey
       end
-    end
 
-    describe "with -a or --admin" do
-      it "should create an admin client" do
-        knife.config[:admin] = true
-        expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("admin" => true)).and_return(client)
+      it "by default it is not a validator" do
         knife.run
+        expect(client.admin).to be_falsey
       end
-    end
 
-    describe "with --validator" do
-      it "should create an validator client" do
-        knife.config[:validator] = true
-        expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("validator" => true)).and_return(client)
+      it "by default it should set create_key to true" do
         knife.run
+        expect(client.create_key).to be_truthy
+      end
+
+      it "should allow you to edit the data" do
+        expect(knife).to receive(:edit_data).with(client).and_return(client)
+        knife.run
+      end
+
+      describe "with -f or --file" do
+        before do
+          client.private_key "woot"
+        end
+
+        it "should write the private key to a file" do
+          knife.config[:file] = "/tmp/monkeypants"
+          filehandle = double("Filehandle")
+          expect(filehandle).to receive(:print).with('woot')
+          expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
+          knife.run
+        end
+      end
+
+      describe "with -a or --admin" do
+        before do
+          knife.config[:admin] = true
+        end
+
+        it "should create an admin client" do
+          knife.run
+          expect(client.admin).to be_truthy
+        end
+      end
+
+      describe "with -p or --public-key" do
+        before do
+          knife.config[:public_key] = 'some_key'
+          allow(File).to receive(:read).and_return('some_key')
+          allow(File).to receive(:expand_path)
+        end
+
+        it "sets the public key" do
+          knife.run
+          expect(client.public_key).to eq('some_key')
+        end
+      end
+
+      describe "with -k or --prevent-keygen" do
+        before do
+          knife.config[:prevent_keygen] = true
+        end
+
+        it "does not set create_key" do
+          knife.run
+          expect(client.create_key).to be_falsey
+        end
+      end
+
+      describe "with --validator" do
+        before do
+          knife.config[:validator] = true
+        end
+
+        it "should create an validator client" do
+          knife.run
+          expect(client.validator).to be_truthy
+        end
       end
     end
   end
diff --git a/spec/unit/knife/client_delete_spec.rb b/spec/unit/knife/client_delete_spec.rb
index 0fb5e0b..6190099 100644
--- a/spec/unit/knife/client_delete_spec.rb
+++ b/spec/unit/knife/client_delete_spec.rb
@@ -30,7 +30,7 @@ describe Chef::Knife::ClientDelete do
 
   describe 'run' do
     it 'should delete the client' do
-      expect(@knife).to receive(:delete_object).with(Chef::ApiClient, 'adam', 'client')
+      expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, 'adam', 'client')
       @knife.run
     end
 
@@ -46,8 +46,8 @@ describe Chef::Knife::ClientDelete do
     before(:each) do
       allow(Chef::Knife::UI).to receive(:confirm).and_return(true)
       allow(@knife).to receive(:confirm).and_return(true)
-      @client = Chef::ApiClient.new
-      expect(Chef::ApiClient).to receive(:load).and_return(@client)
+      @client = Chef::ApiClientV1.new
+      expect(Chef::ApiClientV1).to receive(:load).and_return(@client)
     end
 
     it 'should delete non-validator client if --delete-validators is not set' do
diff --git a/spec/unit/knife/client_edit_spec.rb b/spec/unit/knife/client_edit_spec.rb
index c040c5e..ad56d92 100644
--- a/spec/unit/knife/client_edit_spec.rb
+++ b/spec/unit/knife/client_edit_spec.rb
@@ -17,16 +17,29 @@
 #
 
 require 'spec_helper'
+require 'chef/api_client_v1'
 
 describe Chef::Knife::ClientEdit do
   before(:each) do
     @knife = Chef::Knife::ClientEdit.new
     @knife.name_args = [ 'adam' ]
+    @knife.config[:disable_editing] = true
   end
 
   describe 'run' do
+    let(:data) {
+      {
+        "name" => "adam",
+        "validator" => false,
+        "admin" => false,
+        "chef_type" => "client",
+        "create_key" => true
+      }
+    }
+
     it 'should edit the client' do
-      expect(@knife).to receive(:edit_object).with(Chef::ApiClient, 'adam')
+      allow(Chef::ApiClientV1).to receive(:load).with('adam').and_return(data)
+      expect(@knife).to receive(:edit_data).with(data).and_return(data)
       @knife.run
     end
 
diff --git a/spec/unit/knife/client_list_spec.rb b/spec/unit/knife/client_list_spec.rb
index eff01da..ce0fa4f 100644
--- a/spec/unit/knife/client_list_spec.rb
+++ b/spec/unit/knife/client_list_spec.rb
@@ -26,7 +26,7 @@ describe Chef::Knife::ClientList do
 
   describe 'run' do
     it 'should list the clients' do
-      expect(Chef::ApiClient).to receive(:list)
+      expect(Chef::ApiClientV1).to receive(:list)
       expect(@knife).to receive(:format_list_for_display)
       @knife.run
     end
diff --git a/spec/unit/knife/client_reregister_spec.rb b/spec/unit/knife/client_reregister_spec.rb
index f1be4ed..7e76324 100644
--- a/spec/unit/knife/client_reregister_spec.rb
+++ b/spec/unit/knife/client_reregister_spec.rb
@@ -41,7 +41,7 @@ describe Chef::Knife::ClientReregister do
 
   context 'when not configured for file output' do
     it 'reregisters the client and prints the key' do
-      expect(Chef::ApiClient).to receive(:reregister).with('adam').and_return(@client_mock)
+      expect(Chef::ApiClientV1).to receive(:reregister).with('adam').and_return(@client_mock)
       @knife.run
       expect(@stdout.string).to match( /foo_key/ )
     end
@@ -49,7 +49,7 @@ describe Chef::Knife::ClientReregister do
 
   context 'when configured for file output' do
     it 'should write the private key to a file' do
-      expect(Chef::ApiClient).to receive(:reregister).with('adam').and_return(@client_mock)
+      expect(Chef::ApiClientV1).to receive(:reregister).with('adam').and_return(@client_mock)
 
       @knife.config[:file] = '/tmp/monkeypants'
       filehandle = StringIO.new
diff --git a/spec/unit/knife/client_show_spec.rb b/spec/unit/knife/client_show_spec.rb
index 8404e8d..73a876c 100644
--- a/spec/unit/knife/client_show_spec.rb
+++ b/spec/unit/knife/client_show_spec.rb
@@ -27,7 +27,7 @@ describe Chef::Knife::ClientShow do
 
   describe 'run' do
     it 'should list the client' do
-      expect(Chef::ApiClient).to receive(:load).with('adam').and_return(@client_mock)
+      expect(Chef::ApiClientV1).to receive(:load).with('adam').and_return(@client_mock)
       expect(@knife).to receive(:format_for_display).with(@client_mock)
       @knife.run
     end
@@ -37,7 +37,7 @@ describe Chef::Knife::ClientShow do
       @stdout = StringIO.new
       allow(@knife.ui).to receive(:stdout).and_return(@stdout)
       fake_client_contents = {"foo"=>"bar", "baz"=>"qux"}
-      expect(Chef::ApiClient).to receive(:load).with('adam').and_return(fake_client_contents)
+      expect(Chef::ApiClientV1).to receive(:load).with('adam').and_return(fake_client_contents)
       @knife.run
       expect(@stdout.string).to eql("{\n  \"foo\": \"bar\",\n  \"baz\": \"qux\"\n}\n")
     end
diff --git a/spec/unit/knife/cookbook_site_share_spec.rb b/spec/unit/knife/cookbook_site_share_spec.rb
index 76e4ec7..a7caca9 100644
--- a/spec/unit/knife/cookbook_site_share_spec.rb
+++ b/spec/unit/knife/cookbook_site_share_spec.rb
@@ -78,21 +78,21 @@ describe Chef::Knife::CookbookSiteShare do
 
     it 'should not fail when given only 1 argument and can determine category' do
       @knife.name_args = ['cookbook_name']
-      expect(@noauth_rest).to receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name").and_return(@category_response)
+      expect(@noauth_rest).to receive(:get_rest).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name").and_return(@category_response)
       expect(@knife).to receive(:do_upload)
       @knife.run
     end
 
     it 'should print error and exit when given only 1 argument and cannot determine category' do
       @knife.name_args = ['cookbook_name']
-      expect(@noauth_rest).to receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name").and_return(@bad_category_response)
+      expect(@noauth_rest).to receive(:get_rest).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name").and_return(@bad_category_response)
       expect(@knife.ui).to receive(:fatal)
       expect { @knife.run }.to raise_error(SystemExit)
     end
 
     it 'should print error and exit when given only 1 argument and Chef::REST throws an exception' do
       @knife.name_args = ['cookbook_name']
-      expect(@noauth_rest).to receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" }
+      expect(@noauth_rest).to receive(:get_rest).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" }
       expect(@knife.ui).to receive(:fatal)
       expect { @knife.run }.to raise_error(SystemExit)
     end
diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb
index 3718cb2..ac44e29 100644
--- a/spec/unit/knife/core/bootstrap_context_spec.rb
+++ b/spec/unit/knife/core/bootstrap_context_spec.rb
@@ -20,7 +20,7 @@ require 'spec_helper'
 require 'chef/knife/core/bootstrap_context'
 
 describe Chef::Knife::Core::BootstrapContext do
-  let(:config) { {:foo => :bar} }
+  let(:config) { {:foo => :bar, :color => true} }
   let(:run_list) { Chef::RunList.new('recipe[tmux]', 'role[base]') }
   let(:chef_config) do
     {
@@ -38,14 +38,21 @@ describe Chef::Knife::Core::BootstrapContext do
     expect{described_class.new(config, run_list, chef_config)}.not_to raise_error
   end
 
-  it "runs chef with the first-boot.json in the _default environment" do
-    expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -E _default"
+  it "runs chef with the first-boot.json with no environment specified" do
+    expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json"
   end
 
   describe "when in verbosity mode" do
-    let(:config) { {:verbosity => 2} }
+    let(:config) { {:verbosity => 2, :color => true} }
     it "adds '-l debug' when verbosity is >= 2" do
-      expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -l debug -E _default"
+      expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -l debug"
+    end
+  end
+
+  describe "when no color value has been set in config" do
+    let(:config) { {:color => false } }
+    it "adds '--no-color' when color is false" do
+      expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json --no-color"
     end
   end
 
@@ -70,7 +77,7 @@ EXPECTED
   describe "alternate chef-client path" do
     let(:chef_config){ {:chef_client_path => '/usr/local/bin/chef-client'} }
     it "runs chef-client from another path when specified" do
-      expect(bootstrap_context.start_chef).to eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json -E _default"
+      expect(bootstrap_context.start_chef).to eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json"
     end
   end
 
@@ -91,12 +98,19 @@ EXPECTED
   end
 
   describe "when bootstrapping into a specific environment" do
-    let(:chef_config){ {:environment => "prodtastic"} }
+    let(:config){ {:environment => "prodtastic", :color => true} }
     it "starts chef in the configured environment" do
       expect(bootstrap_context.start_chef).to eq('chef-client -j /etc/chef/first-boot.json -E prodtastic')
     end
   end
 
+  describe "when tags are given" do
+    let(:config) { {:tags => [ "unicorn" ] } }
+    it "adds the attributes to first_boot" do
+      expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({:run_list => run_list, :tags => ["unicorn"]}))
+    end
+  end
+
   describe "when JSON attributes are given" do
     let(:config) { {:first_boot_attributes => {:baz => :quux}} }
     it "adds the attributes to first_boot" do
@@ -110,6 +124,16 @@ EXPECTED
     end
   end
 
+  describe "when policy_name and policy_group are present in config" do
+
+    let(:config) { { policy_name: "my_app_server", policy_group: "staging" } }
+
+    it "includes them in the first_boot data and excludes run_list" do
+      expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json(config))
+    end
+
+  end
+
   describe "when an encrypted_data_bag_secret is provided" do
     let(:secret) { "supersekret" }
     it "reads the encrypted_data_bag_secret" do
diff --git a/spec/unit/knife/core/custom_manifest_loader_spec.rb b/spec/unit/knife/core/custom_manifest_loader_spec.rb
new file mode 100644
index 0000000..1edbedd
--- /dev/null
+++ b/spec/unit/knife/core/custom_manifest_loader_spec.rb
@@ -0,0 +1,41 @@
+#
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+describe Chef::Knife::SubcommandLoader::CustomManifestLoader do
+  let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" }
+  let(:manifest_content) do
+    { "plugins" => {
+        "knife-ec2" => {
+          "paths" => [
+                      ec2_server_create_plugin
+                     ]
+        }
+      }
+    }
+  end
+  let(:loader) do
+    Chef::Knife::SubcommandLoader::CustomManifestLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'),
+                                                            manifest_content)
+  end
+
+  it "uses paths from the manifest instead of searching gems" do
+    expect(Gem::Specification).not_to receive(:latest_specs).and_call_original
+    expect(loader.subcommand_files).to include(ec2_server_create_plugin)
+  end
+end
diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/gem_glob_loader_spec.rb
similarity index 72%
copy from spec/unit/knife/core/subcommand_loader_spec.rb
copy to spec/unit/knife/core/gem_glob_loader_spec.rb
index 7f9308b..465eea2 100644
--- a/spec/unit/knife/core/subcommand_loader_spec.rb
+++ b/spec/unit/knife/core/gem_glob_loader_spec.rb
@@ -1,6 +1,5 @@
 #
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,18 +17,18 @@
 
 require 'spec_helper'
 
-describe Chef::Knife::SubcommandLoader do
-  let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands')) }
-  let(:home) { File.join(CHEF_SPEC_DATA, 'knife-home') }
-  let(:plugin_dir) { File.join(home, '.chef', 'plugins', 'knife') }
-  
-  before do
-    allow(Chef::Platform).to receive(:windows?) { false }
-    Chef::Util::PathHelper.class_variable_set(:@@home_dir, home) 
-  end
+describe Chef::Knife::SubcommandLoader::GemGlobLoader do
+   let(:loader) { Chef::Knife::SubcommandLoader::GemGlobLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands')) }
+   let(:home) { File.join(CHEF_SPEC_DATA, 'knife-home') }
+   let(:plugin_dir) { File.join(home, '.chef', 'plugins', 'knife') }
+
+   before do
+     allow(ChefConfig).to receive(:windows?) { false }
+     Chef::Util::PathHelper.class_variable_set(:@@home_dir, home)
+   end
 
   after do
-    Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil) 
+    Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
   end
 
   it "builds a list of the core subcommand file require paths" do
@@ -61,7 +60,7 @@ describe Chef::Knife::SubcommandLoader do
       expect(Dir).to receive(:[]).with('/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb').and_return(gem_files)
     end
     expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
-    expect(loader.find_subcommands_via_rubygems.values.select { |file| file =~ /knife-ec2/ }.sort).to eq(gem_files)
+    expect(loader.subcommand_files.select { |file| file =~ /knife-ec2/ }.sort).to eq(gem_files)
   end
 
   it "finds files using a dirglob when rubygems is not available" do
@@ -106,6 +105,18 @@ describe Chef::Knife::SubcommandLoader do
         # Chef 12.0.0.rc.0 gem also:
         "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb",
 
+        # Test that we ignore the platform suffix when checking for different
+        # gem versions.
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
+        # ...but don't ignore the .rc / .dev parts in the case when we have
+        # platform suffixes
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0-x86-mingw32/lib/chef/knife/invalid.rb",
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev-mswin32/lib/chef/knife/invalid-too.rb",
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev.0-x86-mingw64/lib/chef/knife/still-invalid.rb",
+
         # This command is "extra" compared to what's in the embedded/apps/chef install:
         "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb",
         "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
@@ -133,6 +144,10 @@ describe Chef::Knife::SubcommandLoader do
         "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
         "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
         "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
+        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
         "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
         "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
         "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
@@ -154,12 +169,30 @@ describe Chef::Knife::SubcommandLoader do
   end
 
   describe "finding 3rd party plugins" do
-    let(:home) { "/home/alice" }
-    let(:manifest_path) { home + "/.chef/plugin_manifest.json" }
+    let(:env_home) { "/home/alice" }
+    let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" }
+
+    before do
+      env_dup = ENV.to_hash
+      allow(ENV).to receive(:[]) { |key| env_dup[key] }
+      allow(ENV).to receive(:[]).with("HOME").and_return(env_home)
+    end
+
 
-    context "when there is not a ~/.chef/plugin_manifest.json file" do
+    it "searches rubygems for plugins" do
+      if Gem::Specification.respond_to?(:latest_specs)
+        expect(Gem::Specification).to receive(:latest_specs).and_call_original
+      else
+        expect(Gem.source_index).to receive(:latest_specs).and_call_original
+      end
+      loader.subcommand_files.each do |require_path|
+        expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
+      end
+    end
+
+    context "and HOME environment variable is not set" do
       before do
-        allow(File).to receive(:exist?).with(manifest_path).and_return(false)
+        allow(ENV).to receive(:[]).with("HOME").and_return(nil)
       end
 
       it "searches rubygems for plugins" do
@@ -172,53 +205,6 @@ describe Chef::Knife::SubcommandLoader do
           expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
         end
       end
-
-      context "and HOME environment variable is not set" do
-        before do
-          allow(Chef::Util::PathHelper).to receive(:home).and_return(nil)
-        end
-
-        it "searches rubygems for plugins" do
-          if Gem::Specification.respond_to?(:latest_specs)
-            expect(Gem::Specification).to receive(:latest_specs).and_call_original
-          else
-            expect(Gem.source_index).to receive(:latest_specs).and_call_original
-          end
-          loader.subcommand_files.each do |require_path|
-            expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
-          end
-        end
-      end
-
-    end
-
-    context "when there is a ~/.chef/plugin_manifest.json file" do
-      let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" }
-
-      let(:manifest_content) do
-        { "plugins" => {
-            "knife-ec2" => {
-              "paths" => [
-                ec2_server_create_plugin
-              ]
-            }
-          }
-        }
-      end
-
-      let(:manifest_json) { Chef::JSONCompat.to_json(manifest_content) }
-
-      before do
-        allow(File).to receive(:exist?).with(manifest_path).and_return(true)
-        allow(File).to receive(:read).with(manifest_path).and_return(manifest_json)
-      end
-
-      it "uses paths from the manifest instead of searching gems" do
-        expect(Gem::Specification).not_to receive(:latest_specs).and_call_original
-        expect(loader.subcommand_files).to include(ec2_server_create_plugin)
-      end
-
     end
   end
-
 end
diff --git a/spec/unit/knife/core/hashed_command_loader_spec.rb b/spec/unit/knife/core/hashed_command_loader_spec.rb
new file mode 100644
index 0000000..00e7ba3
--- /dev/null
+++ b/spec/unit/knife/core/hashed_command_loader_spec.rb
@@ -0,0 +1,93 @@
+#
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+describe Chef::Knife::SubcommandLoader::HashedCommandLoader do
+  before do
+    allow(ChefConfig).to receive(:windows?) { false }
+  end
+
+  let(:plugin_manifest) {
+    {
+      "_autogenerated_command_paths" => {
+        "plugins_paths" => {
+          "cool_a" => ["/file/for/plugin/a"],
+          "cooler_b" => ["/file/for/plugin/b"]
+        },
+        "plugins_by_category" => {
+          "cool" => [
+            "cool_a"
+          ],
+          "cooler" => [
+            "cooler_b"
+          ]
+        }
+      }
+    }
+  }
+
+  let(:loader) { Chef::Knife::SubcommandLoader::HashedCommandLoader.new(
+    File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'),
+    plugin_manifest)}
+
+  describe "#list_commands" do
+    it "lists all commands by category when no argument is given" do
+      expect(loader.list_commands).to eq({"cool" => ["cool_a"], "cooler" => ["cooler_b"]})
+    end
+
+    it "lists only commands in the given category when a category is given" do
+      expect(loader.list_commands("cool")).to eq({"cool" => ["cool_a"]})
+    end
+  end
+
+  describe "#subcommand_files" do
+    it "lists all the files" do
+      expect(loader.subcommand_files).to eq(["/file/for/plugin/a", "/file/for/plugin/b"])
+    end
+  end
+
+  describe "#load_commands" do
+    before do
+      allow(Kernel).to receive(:load).and_return(true)
+    end
+
+    it "returns false for non-existant commands" do
+      expect(loader.load_command(["nothere"])).to eq(false)
+    end
+
+    it "loads the correct file and returns true if the command exists" do
+      allow(File).to receive(:exists?).and_return(true)
+      expect(Kernel).to receive(:load).with("/file/for/plugin/a").and_return(true)
+      expect(loader.load_command(["cool_a"])).to eq(true)
+    end
+  end
+
+  describe "#subcommand_for_args" do
+    it "returns the subcommands for an exact match" do
+      expect(loader.subcommand_for_args(["cooler_b"])).to eq("cooler_b")
+    end
+
+    it "finds the right subcommand even when _'s are elided" do
+      expect(loader.subcommand_for_args(["cooler", "b"])).to eq("cooler_b")
+    end
+
+    it "returns nil if the the subcommand isn't in our manifest" do
+      expect(loader.subcommand_for_args(["cooler c"])).to eq(nil)
+    end
+  end
+end
diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb
index 7f9308b..2386465 100644
--- a/spec/unit/knife/core/subcommand_loader_spec.rb
+++ b/spec/unit/knife/core/subcommand_loader_spec.rb
@@ -1,6 +1,5 @@
 #
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,203 +21,44 @@ describe Chef::Knife::SubcommandLoader do
   let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands')) }
   let(:home) { File.join(CHEF_SPEC_DATA, 'knife-home') }
   let(:plugin_dir) { File.join(home, '.chef', 'plugins', 'knife') }
-  
+
   before do
-    allow(Chef::Platform).to receive(:windows?) { false }
-    Chef::Util::PathHelper.class_variable_set(:@@home_dir, home) 
+    allow(ChefConfig).to receive(:windows?) { false }
+    Chef::Util::PathHelper.class_variable_set(:@@home_dir, home)
   end
 
   after do
-    Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil) 
-  end
-
-  it "builds a list of the core subcommand file require paths" do
-    expect(loader.subcommand_files).not_to be_empty
-    loader.subcommand_files.each do |require_path|
-      expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
-    end
-  end
-
-  it "finds files installed via rubygems" do
-    expect(loader.find_subcommands_via_rubygems).to include('chef/knife/node_create')
-    loader.find_subcommands_via_rubygems.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])}
-  end
-
-  it "finds files from latest version of installed gems" do
-    gems = [ double('knife-ec2-0.5.12') ]
-    gem_files = [
-      '/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb',
-      '/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb'
-    ]
-    expect($LOAD_PATH).to receive(:map).and_return([])
-    if Gem::Specification.respond_to? :latest_specs
-      expect(Gem::Specification).to receive(:latest_specs).with(true).and_return(gems)
-      expect(gems[0]).to receive(:matches_for_glob).with(/chef\/knife\/\*\.rb\{(.*),\.rb,(.*)\}/).and_return(gem_files)
-    else
-      expect(Gem.source_index).to receive(:latest_specs).with(true).and_return(gems)
-      expect(gems[0]).to receive(:require_paths).twice.and_return(['lib'])
-      expect(gems[0]).to receive(:full_gem_path).and_return('/usr/lib/ruby/gems/knife-ec2-0.5.12')
-      expect(Dir).to receive(:[]).with('/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb').and_return(gem_files)
-    end
-    expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
-    expect(loader.find_subcommands_via_rubygems.values.select { |file| file =~ /knife-ec2/ }.sort).to eq(gem_files)
-  end
-
-  it "finds files using a dirglob when rubygems is not available" do
-    expect(loader.find_subcommands_via_dirglob).to include('chef/knife/node_create')
-    loader.find_subcommands_via_dirglob.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])}
-  end
-
-  it "finds user-specific subcommands in the user's ~/.chef directory" do
-    expected_command = File.join(home, '.chef', 'plugins', 'knife', 'example_home_subcommand.rb')
-    expect(loader.site_subcommands).to include(expected_command)
-  end
-
-  it "finds repo specific subcommands by searching for a .chef directory" do
-    expected_command = File.join(CHEF_SPEC_DATA, 'knife-site-subcommands', 'plugins', 'knife', 'example_subcommand.rb')
-    expect(loader.site_subcommands).to include(expected_command)
+    Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
   end
 
-  # https://github.com/opscode/chef-dk/issues/227
-  #
-  # `knife` in ChefDK isn't from a gem install, it's directly run from a clone
-  # of the source, but there can be one or more versions of chef also installed
-  # as a gem. If the gem install contains a command that doesn't exist in the
-  # source tree of the "primary" chef install, it can be loaded and cause an
-  # error. We also want to ensure that we only load builtin commands from the
-  # "primary" chef install.
-  context "when a different version of chef is also installed as a gem" do
-
-    let(:all_found_commands) do
-      [
-        "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
-        "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
-        "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
-
-        # We use the fake version 1.0.0 because that version doesn't exist,
-        # which ensures it won't ever equal "chef-#{Chef::VERSION}"
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/bootstrap.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_bulk_delete.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_create.rb",
-
-        # Test that we don't accept a version number that is different only in
-        # trailing characters, e.g. we are running Chef 12.0.0 but there is a
-        # Chef 12.0.0.rc.0 gem also:
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb",
-
-        # This command is "extra" compared to what's in the embedded/apps/chef install:
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
-
-        # These are fake commands that have names designed to test that the
-        # regex is strict enough
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
+  let(:config_dir) { File.join(CHEF_SPEC_DATA, 'knife-site-subcommands') }
 
-        # In a real scenario, we'd use rubygems APIs to only select the most
-        # recent gem, but for this test we want to check that we're doing the
-        # right thing both when the plugin version matches and does not match
-        # the current chef version. Looking at
-        # `SubcommandLoader::MATCHES_THIS_CHEF_GEM` and
-        # `SubcommandLoader::MATCHES_CHEF_GEM` should make it clear why we want
-        # to test these two cases.
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb"
-      ]
-    end
-
-    let(:expected_valid_commands) do
-      [
-        "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
-        "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
-        "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
-        "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb"
-      ]
-    end
-
-    before do
-      expect(loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands)
-      expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
-    end
-
-    it "ignores commands from the non-matching gem install" do
-      expect(loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands)
-    end
-
-  end
-
-  describe "finding 3rd party plugins" do
-    let(:home) { "/home/alice" }
-    let(:manifest_path) { home + "/.chef/plugin_manifest.json" }
-
-    context "when there is not a ~/.chef/plugin_manifest.json file" do
+  describe "#for_config" do
+    context "when ~/.chef/plugin_manifest.json exists" do
       before do
-        allow(File).to receive(:exist?).with(manifest_path).and_return(false)
+        allow(File).to receive(:exist?).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return(true)
       end
 
-      it "searches rubygems for plugins" do
-        if Gem::Specification.respond_to?(:latest_specs)
-          expect(Gem::Specification).to receive(:latest_specs).and_call_original
-        else
-          expect(Gem.source_index).to receive(:latest_specs).and_call_original
-        end
-        loader.subcommand_files.each do |require_path|
-          expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
-        end
+      it "creates a HashedCommandLoader with the manifest has _autogenerated_command_paths" do
+        allow(File).to receive(:read).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return("{ \"_autogenerated_command_paths\": {}}")
+        expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::HashedCommandLoader
       end
 
-      context "and HOME environment variable is not set" do
-        before do
-          allow(Chef::Util::PathHelper).to receive(:home).and_return(nil)
-        end
-
-        it "searches rubygems for plugins" do
-          if Gem::Specification.respond_to?(:latest_specs)
-            expect(Gem::Specification).to receive(:latest_specs).and_call_original
-          else
-            expect(Gem.source_index).to receive(:latest_specs).and_call_original
-          end
-          loader.subcommand_files.each do |require_path|
-            expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
-          end
-        end
+      it "creates a CustomManifestLoader with then manifest has a key other than _autogenerated_command_paths" do
+        Chef::Config[:treat_deprecation_warnings_as_errors] = false
+        allow(File).to receive(:read).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return("{ \"plugins\": {}}")
+        expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::CustomManifestLoader
       end
-
     end
 
-    context "when there is a ~/.chef/plugin_manifest.json file" do
-      let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" }
-
-      let(:manifest_content) do
-        { "plugins" => {
-            "knife-ec2" => {
-              "paths" => [
-                ec2_server_create_plugin
-              ]
-            }
-          }
-        }
-      end
-
-      let(:manifest_json) { Chef::JSONCompat.to_json(manifest_content) }
-
+    context "when ~/.chef/plugin_manifest.json does not exist" do
       before do
-        allow(File).to receive(:exist?).with(manifest_path).and_return(true)
-        allow(File).to receive(:read).with(manifest_path).and_return(manifest_json)
+        allow(File).to receive(:exist?).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return(false)
       end
 
-      it "uses paths from the manifest instead of searching gems" do
-        expect(Gem::Specification).not_to receive(:latest_specs).and_call_original
-        expect(loader.subcommand_files).to include(ec2_server_create_plugin)
+      it "creates a GemGlobLoader" do
+        expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader
       end
-
     end
   end
-
 end
diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb
index ac42ad6..ab42051 100644
--- a/spec/unit/knife/core/ui_spec.rb
+++ b/spec/unit/knife/core/ui_spec.rb
@@ -368,6 +368,20 @@ EOM
         @ui.config[:attribute] = "keys.keys"
         expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys.keys" => "values" } })
       end
+
+      it "should return the name attribute" do
+        allow_any_instance_of(Chef::Node).to receive(:name).and_return("chef.localdomain")
+        input = Chef::Node.new
+        @ui.config[:attribute] = "name"
+        expect(@ui.format_for_display(input)).to eq( {"chef.localdomain"=>{"name"=>"chef.localdomain"} })
+      end
+
+      it "returns nil when given an attribute path that isn't a name or attribute" do
+        input = { "keys" =>  {"keys" => "values"}, "hi" => "ho", "id" => "sample-data-bag-item" }
+        non_existing_path = "nope.nada.nothingtoseehere"
+        @ui.config[:attribute] = non_existing_path
+        expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { non_existing_path => nil } })
+      end
     end
 
     describe "with --run-list passed" do
@@ -420,7 +434,7 @@ EOM
       before(:each) do
         stdout = double('StringIO', :tty? => true)
         allow(@ui).to receive(:stdout).and_return(stdout)
-        allow(Chef::Platform).to receive(:windows?) { true }
+        allow(ChefConfig).to receive(:windows?) { true }
         Chef::Config.reset
       end
 
diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb
index 3882bff..8b65021 100644
--- a/spec/unit/knife/data_bag_from_file_spec.rb
+++ b/spec/unit/knife/data_bag_from_file_spec.rb
@@ -26,7 +26,7 @@ Chef::Knife::DataBagFromFile.load_deps
 
 describe Chef::Knife::DataBagFromFile do
   before :each do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
     Chef::Config[:node_name] = "webmonkey.example.com"
     FileUtils.mkdir_p([db_folder, db_folder2])
     db_file.write(Chef::JSONCompat.to_json(plain_data))
diff --git a/spec/unit/knife/environment_from_file_spec.rb b/spec/unit/knife/environment_from_file_spec.rb
index d150e5e..11ad23c 100644
--- a/spec/unit/knife/environment_from_file_spec.rb
+++ b/spec/unit/knife/environment_from_file_spec.rb
@@ -23,7 +23,7 @@ Chef::Knife::EnvironmentFromFile.load_deps
 
 describe Chef::Knife::EnvironmentFromFile do
   before(:each) do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
     @knife = Chef::Knife::EnvironmentFromFile.new
     @stdout = StringIO.new
     allow(@knife.ui).to receive(:stdout).and_return(@stdout)
diff --git a/spec/unit/knife/key_create_spec.rb b/spec/unit/knife/key_create_spec.rb
new file mode 100644
index 0000000..5998e10
--- /dev/null
+++ b/spec/unit/knife/key_create_spec.rb
@@ -0,0 +1,224 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/knife/user_key_create'
+require 'chef/knife/client_key_create'
+require 'chef/knife/key_create'
+require 'chef/key'
+
+describe "key create commands that inherit knife" do
+  shared_examples_for "a key create command" do
+    let(:stderr) { StringIO.new }
+    let(:params) { [] }
+    let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
+    let(:command) do
+      c = described_class.new([])
+      c.ui.config[:disable_editing] = true
+      allow(c.ui).to receive(:stderr).and_return(stderr)
+      allow(c.ui).to receive(:stdout).and_return(stderr)
+      allow(c).to receive(:show_usage)
+      c
+    end
+
+    context "after apply_params! is called with valid args" do
+      let(:params) { ["charmander"] }
+      before do
+        command.apply_params!(params)
+      end
+
+      context "when the service object is called" do
+        it "creates a new instance of Chef::Knife::KeyCreate with the correct args" do
+          expect(Chef::Knife::KeyCreate).to receive(:new).
+            with("charmander", command.actor_field_name, command.ui, command.config).
+            and_return(service_object)
+          command.service_object
+        end
+      end # when the service object is called
+    end # after apply_params! is called with valid args
+  end # a key create command
+
+  describe Chef::Knife::UserKeyCreate do
+    it_should_behave_like "a key create command"
+    # defined in key_helper.rb
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
+      let(:params) { ["charmander"] }
+    end
+  end
+
+  describe Chef::Knife::ClientKeyCreate do
+    it_should_behave_like "a key create command"
+    # defined in key_helper.rb
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
+      let(:params) { ["charmander"] }
+    end
+  end
+end
+
+describe Chef::Knife::KeyCreate do
+  let(:public_key) {
+    "-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
+KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
+WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
+E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
+IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
+Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
+0wIDAQAB
+-----END PUBLIC KEY-----"
+  }
+  let(:config) { Hash.new }
+  let(:actor) { "charmander" }
+  let(:ui) { instance_double("Chef::Knife::UI") }
+
+  shared_examples_for "key create run command" do
+    let(:key_create_object) {
+      described_class.new(actor, actor_field_name, ui, config)
+    }
+
+    context "when public_key and key_name weren't passed" do
+      it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
+        expect{ key_create_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_create_object.public_key_or_key_name_error_msg)
+      end
+    end
+
+    context "when the command is run" do
+      let(:expected_hash) {
+        {
+          actor_field_name => "charmander"
+        }
+      }
+
+      before do
+        allow(File).to receive(:read).and_return(public_key)
+        allow(File).to receive(:expand_path)
+
+        allow(key_create_object).to receive(:output_private_key_to_file)
+        allow(key_create_object).to receive(:display_private_key)
+        allow(key_create_object).to receive(:edit_data).and_return(expected_hash)
+        allow(key_create_object).to receive(:create_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+        allow(key_create_object).to receive(:display_info)
+      end
+
+      context "when a valid hash is passed" do
+        let(:key_name) { "charmander-key" }
+        let(:valid_expiration_date) { "2020-12-24T21:00:00Z" }
+        let(:expected_hash) {
+          {
+            actor_field_name => "charmander",
+            "public_key" => public_key,
+            "expiration_date" => valid_expiration_date,
+            "key_name" => key_name
+          }
+        }
+        before do
+          key_create_object.config[:public_key] = "public_key_path"
+          key_create_object.config[:expiration_Date] = valid_expiration_date,
+          key_create_object.config[:key_name] = key_name
+        end
+
+        it "creates the proper hash" do
+          expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash)
+          key_create_object.run
+        end
+      end
+
+      context "when public_key is passed" do
+        let(:expected_hash) {
+          {
+            actor_field_name => "charmander",
+            "public_key" => public_key
+          }
+        }
+        before do
+          key_create_object.config[:public_key] = "public_key_path"
+        end
+
+        it "calls File.expand_path with the public_key input" do
+          expect(File).to receive(:expand_path).with("public_key_path")
+          key_create_object.run
+        end
+      end # when public_key is passed
+
+      context "when public_key isn't passed and key_name is" do
+        let(:expected_hash) {
+          {
+            actor_field_name => "charmander",
+            "name" => "charmander-key",
+            "create_key" => true
+          }
+        }
+        before do
+          key_create_object.config[:key_name] = "charmander-key"
+        end
+
+        it "should set create_key to true" do
+          expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash)
+          key_create_object.run
+        end
+      end
+
+      context "when the server returns a private key" do
+        let(:expected_hash) {
+          {
+            actor_field_name => "charmander",
+            "public_key" => public_key,
+            "private_key" => "super_private"
+          }
+        }
+
+        before do
+          key_create_object.config[:public_key] = "public_key_path"
+        end
+
+        context "when file is not passed" do
+          it "calls display_private_key with the private_key" do
+            expect(key_create_object).to receive(:display_private_key).with("super_private")
+            key_create_object.run
+          end
+        end
+
+        context "when file is passed" do
+          before do
+            key_create_object.config[:file] = "/fake/file"
+          end
+
+          it "calls output_private_key_to_file with the private_key" do
+            expect(key_create_object).to receive(:output_private_key_to_file).with("super_private")
+            key_create_object.run
+          end
+        end
+      end # when the server returns a private key
+    end # when the command is run
+  end #key create run command"
+
+  context "when actor_field_name is 'user'" do
+    it_should_behave_like "key create run command" do
+      let(:actor_field_name) { "user" }
+    end
+  end
+
+  context "when actor_field_name is 'client'" do
+    it_should_behave_like "key create run command" do
+      let(:actor_field_name) { "client" }
+    end
+  end
+end
+
diff --git a/spec/unit/knife/key_delete_spec.rb b/spec/unit/knife/key_delete_spec.rb
new file mode 100644
index 0000000..1d4b9f8
--- /dev/null
+++ b/spec/unit/knife/key_delete_spec.rb
@@ -0,0 +1,135 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/knife/user_key_delete'
+require 'chef/knife/client_key_delete'
+require 'chef/knife/key_delete'
+require 'chef/key'
+
+describe "key delete commands that inherit knife" do
+  shared_examples_for "a key delete command" do
+    let(:stderr) { StringIO.new }
+    let(:params) { [] }
+    let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
+    let(:command) do
+      c = described_class.new([])
+      c.ui.config[:disable_editing] = true
+      allow(c.ui).to receive(:stderr).and_return(stderr)
+      allow(c.ui).to receive(:stdout).and_return(stderr)
+      allow(c).to receive(:show_usage)
+      c
+    end
+
+    context "after apply_params! is called with valid args" do
+      let(:params) { ["charmander", "charmander-key"] }
+      before do
+        command.apply_params!(params)
+      end
+
+      context "when the service object is called" do
+        it "creates a new instance of Chef::Knife::KeyDelete with the correct args" do
+          expect(Chef::Knife::KeyDelete).to receive(:new).
+                                             with("charmander-key", "charmander", command.actor_field_name, command.ui).
+                                             and_return(service_object)
+          command.service_object
+        end
+      end # when the service object is called
+    end # after apply_params! is called with valid args
+  end # a key delete command
+
+  describe Chef::Knife::UserKeyDelete do
+    it_should_behave_like "a key delete command"
+    # defined in key_helpers.rb
+    it_should_behave_like "a knife key command with a keyname as the second arg"
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
+      let(:params) { ["charmander", "charmander-key"] }
+    end
+  end
+
+  describe Chef::Knife::ClientKeyDelete do
+    it_should_behave_like "a key delete command"
+    # defined in key_helpers.rb
+    it_should_behave_like "a knife key command with a keyname as the second arg"
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
+      let(:params) { ["charmander", "charmander-key"] }
+    end
+  end
+end
+
+describe Chef::Knife::KeyDelete do
+  let(:actor) { "charmander" }
+  let(:keyname) { "charmander-key" }
+  let(:ui) { instance_double("Chef::Knife::UI") }
+
+  shared_examples_for "key delete run command" do
+    let(:key_delete_object) {
+      described_class.new(keyname, actor, actor_field_name, ui)
+    }
+
+    before do
+      allow_any_instance_of(Chef::Key).to receive(:destroy)
+      allow(key_delete_object).to receive(:print_destroyed)
+      allow(key_delete_object).to receive(:confirm!)
+    end
+
+    context "when the command is run" do
+      it "calls Chef::Key.new with the proper input" do
+        expect(Chef::Key).to receive(:new).with(actor, actor_field_name).and_call_original
+        key_delete_object.run
+      end
+
+      it "calls name on the Chef::Key instance with the proper input" do
+        expect_any_instance_of(Chef::Key).to receive(:name).with(keyname)
+        key_delete_object.run
+      end
+
+      it "calls destroy on the Chef::Key instance" do
+        expect_any_instance_of(Chef::Key).to receive(:destroy).once
+        key_delete_object.run
+      end
+
+      it "calls confirm!" do
+        expect(key_delete_object).to receive(:confirm!)
+        key_delete_object.run
+      end
+
+      it "calls print_destroyed" do
+        expect(key_delete_object).to receive(:print_destroyed)
+        key_delete_object.run
+      end
+    end # when the command is run
+
+
+  end # key delete run command
+
+  context "when actor_field_name is 'user'" do
+    it_should_behave_like "key delete run command" do
+      let(:actor_field_name) { "user" }
+    end
+  end
+
+  context "when actor_field_name is 'client'" do
+    it_should_behave_like "key delete run command" do
+      let(:actor_field_name) { "client" }
+    end
+  end
+end
+
diff --git a/spec/unit/knife/key_edit_spec.rb b/spec/unit/knife/key_edit_spec.rb
new file mode 100644
index 0000000..538b91d
--- /dev/null
+++ b/spec/unit/knife/key_edit_spec.rb
@@ -0,0 +1,267 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/knife/user_key_edit'
+require 'chef/knife/client_key_edit'
+require 'chef/knife/key_edit'
+require 'chef/key'
+
+describe "key edit commands that inherit knife" do
+  shared_examples_for "a key edit command" do
+    let(:stderr) { StringIO.new }
+    let(:params) { [] }
+    let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
+    let(:command) do
+      c = described_class.new([])
+      c.ui.config[:disable_editing] = true
+      allow(c.ui).to receive(:stderr).and_return(stderr)
+      allow(c.ui).to receive(:stdout).and_return(stderr)
+      allow(c).to receive(:show_usage)
+      c
+    end
+
+    context "after apply_params! is called with valid args" do
+      let(:params) { ["charmander", "charmander-key"] }
+      before do
+        command.apply_params!(params)
+      end
+
+      context "when the service object is called" do
+        it "creates a new instance of Chef::Knife::KeyEdit with the correct args" do
+          expect(Chef::Knife::KeyEdit).to receive(:new).
+                                             with("charmander-key", "charmander", command.actor_field_name, command.ui, command.config).
+                                             and_return(service_object)
+          command.service_object
+        end
+      end # when the service object is called
+    end # after apply_params! is called with valid args
+  end # a key edit command
+
+  describe Chef::Knife::UserKeyEdit do
+    it_should_behave_like "a key edit command"
+    # defined in key_helpers.rb
+    it_should_behave_like "a knife key command with a keyname as the second arg"
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
+      let(:params) { ["charmander", "charmander-key"] }
+    end
+  end
+
+  describe Chef::Knife::ClientKeyEdit do
+    it_should_behave_like "a key edit command"
+    # defined in key_helpers.rb
+    it_should_behave_like "a knife key command with a keyname as the second arg"
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
+      let(:params) { ["charmander", "charmander-key"] }
+    end
+  end
+end
+
+describe Chef::Knife::KeyEdit do
+  let(:public_key) {
+    "-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
+KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
+WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
+E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
+IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
+Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
+0wIDAQAB
+-----END PUBLIC KEY-----"
+  }
+  let(:config) { Hash.new }
+  let(:actor) { "charmander" }
+  let(:keyname) { "charmander-key" }
+  let(:ui) { instance_double("Chef::Knife::UI") }
+
+  shared_examples_for "key edit run command" do
+    let(:key_edit_object) {
+      described_class.new(keyname, actor, actor_field_name, ui, config)
+    }
+
+    context "when the command is run" do
+      let(:expected_hash) {
+        {
+          actor_field_name => "charmander"
+        }
+      }
+      let(:new_keyname) { "charizard-key" }
+
+      before do
+        allow(File).to receive(:read).and_return(public_key)
+        allow(File).to receive(:expand_path)
+
+        allow(key_edit_object).to receive(:output_private_key_to_file)
+        allow(key_edit_object).to receive(:display_private_key)
+        allow(key_edit_object).to receive(:edit_data).and_return(expected_hash)
+        allow(key_edit_object).to receive(:display_info)
+      end
+
+
+      context "when public_key and create_key are passed" do
+        before do
+          key_edit_object.config[:public_key] = "public_key_path"
+          key_edit_object.config[:create_key] = true
+        end
+
+        it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
+          expect{ key_edit_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_edit_object.public_key_and_create_key_error_msg)
+        end
+      end
+
+      context "when key_name is passed" do
+        let(:expected_hash) {
+          {
+            actor_field_name => "charmander",
+            "name" => new_keyname
+          }
+        }
+        before do
+          key_edit_object.config[:key_name] = new_keyname
+          allow_any_instance_of(Chef::Key).to receive(:update)
+        end
+
+        it "update_key_from_hash gets passed a hash with new key name" do
+          expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash).and_return(Chef::Key.from_hash(expected_hash))
+          key_edit_object.run
+        end
+
+        it "Chef::Key.update is passed a string containing the original keyname" do
+          expect_any_instance_of(Chef::Key).to receive(:update).with(/#{keyname}/).and_return(Chef::Key.from_hash(expected_hash))
+          key_edit_object.run
+        end
+
+        it "Chef::Key.update is not passed a string containing the new keyname" do
+          expect_any_instance_of(Chef::Key).not_to receive(:update).with(/#{new_keyname}/)
+          allow_any_instance_of(Chef::Key).to receive(:update).and_return(Chef::Key.from_hash(expected_hash))
+          key_edit_object.run
+        end
+      end
+
+      context "when public_key, key_name, and expiration_date are passed" do
+        let(:expected_hash) {
+          {
+            actor_field_name => "charmander",
+            "public_key" => public_key,
+            "name" => new_keyname,
+            "expiration_date" => "infinity"
+          }
+        }
+        before do
+          key_edit_object.config[:public_key] = "this-public-key"
+          key_edit_object.config[:key_name] = new_keyname
+          key_edit_object.config[:expiration_date] = "infinity"
+          allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+        end
+
+        it "passes the right hash to update_key_from_hash" do
+          expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash)
+          key_edit_object.run
+        end
+      end
+
+      context "when create_key is passed" do
+        let(:expected_hash) {
+          {
+            actor_field_name => "charmander",
+            "create_key" => true
+          }
+        }
+
+        before do
+          key_edit_object.config[:create_key] = true
+          allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+        end
+
+        it "passes the right hash to update_key_from_hash" do
+          expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash)
+          key_edit_object.run
+        end
+      end
+
+      context "when public_key is passed" do
+        let(:expected_hash) {
+          {
+            actor_field_name => "charmander",
+            "public_key" => public_key
+          }
+        }
+        before do
+          allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+          key_edit_object.config[:public_key] = "public_key_path"
+        end
+
+        it "calls File.expand_path with the public_key input" do
+          expect(File).to receive(:expand_path).with("public_key_path")
+          key_edit_object.run
+        end
+      end # when public_key is passed
+
+      context "when the server returns a private key" do
+        let(:expected_hash) {
+          {
+            actor_field_name => "charmander",
+            "public_key" => public_key,
+            "private_key" => "super_private"
+          }
+        }
+
+        before do
+          allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+          key_edit_object.config[:public_key] = "public_key_path"
+        end
+
+        context "when file is not passed" do
+          it "calls display_private_key with the private_key" do
+            expect(key_edit_object).to receive(:display_private_key).with("super_private")
+            key_edit_object.run
+          end
+        end
+
+        context "when file is passed" do
+          before do
+            key_edit_object.config[:file] = "/fake/file"
+          end
+
+          it "calls output_private_key_to_file with the private_key" do
+            expect(key_edit_object).to receive(:output_private_key_to_file).with("super_private")
+            key_edit_object.run
+          end
+        end
+      end # when the server returns a private key
+
+    end # when the command is run
+
+
+
+  end # key edit run command
+
+  context "when actor_field_name is 'user'" do
+    it_should_behave_like "key edit run command" do
+      let(:actor_field_name) { "user" }
+    end
+  end
+
+  context "when actor_field_name is 'client'" do
+    it_should_behave_like "key edit run command" do
+      let(:actor_field_name) { "client" }
+    end
+  end
+end
diff --git a/spec/unit/knife/key_helper.rb b/spec/unit/knife/key_helper.rb
new file mode 100644
index 0000000..36ababc
--- /dev/null
+++ b/spec/unit/knife/key_helper.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+shared_examples_for "a knife key command" do
+  let(:stderr) { StringIO.new }
+  let(:params) { [] }
+  let(:command) do
+    c = described_class.new([])
+    c.ui.config[:disable_editing] = true
+    allow(c.ui).to receive(:stderr).and_return(stderr)
+    allow(c.ui).to receive(:stdout).and_return(stderr)
+    allow(c).to receive(:show_usage)
+    c
+  end
+
+  context "before apply_params! is called" do
+    context "when apply_params! is called with invalid args" do
+      it "shows the usage" do
+        expect(command).to receive(:show_usage)
+        expect { command.apply_params!(params) }.to exit_with_code(1)
+      end
+
+      it "outputs the proper error" do
+        expect { command.apply_params!(params) }.to exit_with_code(1)
+        expect(stderr.string).to include(command.actor_missing_error)
+      end
+
+      it "exits 1" do
+        expect { command.apply_params!(params) }.to exit_with_code(1)
+      end
+    end
+  end # before apply_params! is called
+
+  context "after apply_params! is called with valid args" do
+    let(:params) { ["charmander"] }
+    before do
+      command.apply_params!(params)
+    end
+
+    it "properly defines the actor" do
+      expect(command.actor).to eq("charmander")
+    end
+  end # after apply_params! is called with valid args
+
+  context "when the command is run" do
+    before do
+      allow(command).to receive(:service_object).and_return(service_object)
+      allow(command).to receive(:name_args).and_return(["charmander"])
+    end
+
+    context "when the command is successful" do
+      before do
+        expect(service_object).to receive(:run)
+      end
+    end
+  end
+end # a knife key command
diff --git a/spec/unit/knife/key_list_spec.rb b/spec/unit/knife/key_list_spec.rb
new file mode 100644
index 0000000..aabe02a
--- /dev/null
+++ b/spec/unit/knife/key_list_spec.rb
@@ -0,0 +1,216 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/knife/user_key_list'
+require 'chef/knife/client_key_list'
+require 'chef/knife/key_list'
+require 'chef/key'
+
+describe "key list commands that inherit knife" do
+  shared_examples_for "a key list command" do
+    let(:stderr) { StringIO.new }
+    let(:params) { [] }
+    let(:service_object) { instance_double(Chef::Knife::KeyList) }
+    let(:command) do
+      c = described_class.new([])
+      c.ui.config[:disable_editing] = true
+      allow(c.ui).to receive(:stderr).and_return(stderr)
+      allow(c.ui).to receive(:stdout).and_return(stderr)
+      allow(c).to receive(:show_usage)
+      c
+    end
+
+    context "after apply_params! is called with valid args" do
+      let(:params) { ["charmander"] }
+      before do
+        command.apply_params!(params)
+      end
+
+      context "when the service object is called" do
+        it "creates a new instance of Chef::Knife::KeyList with the correct args" do
+          expect(Chef::Knife::KeyList).to receive(:new).
+            with("charmander", command.list_method, command.ui, command.config).
+            and_return(service_object)
+          command.service_object
+        end
+      end # when the service object is called
+    end # after apply_params! is called with valid args
+  end # a key list command
+
+  describe Chef::Knife::UserKeyList do
+    it_should_behave_like "a key list command"
+    # defined in key_helpers.rb
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyList) }
+      let(:params) { ["charmander"] }
+    end
+  end
+
+  describe Chef::Knife::ClientKeyList do
+    it_should_behave_like "a key list command"
+    # defined in key_helpers.rb
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyList) }
+      let(:params) { ["charmander"] }
+    end
+  end
+end
+
+describe Chef::Knife::KeyList do
+  let(:config) { Hash.new }
+  let(:actor) { "charmander" }
+  let(:ui) { instance_double("Chef::Knife::UI") }
+
+  shared_examples_for "key list run command" do
+    let(:key_list_object) {
+      described_class.new(actor, list_method, ui, config)
+    }
+
+    before do
+      allow(Chef::Key).to receive(list_method).and_return(http_response)
+      allow(key_list_object).to receive(:display_info)
+      # simply pass the string though that colorize takes in
+      allow(key_list_object).to receive(:colorize).with(kind_of(String)) do |input|
+        input
+      end
+    end
+
+    context "when only_expired and only_non_expired were both passed" do
+      before do
+        key_list_object.config[:only_expired] = true
+        key_list_object.config[:only_non_expired] = true
+      end
+
+      it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
+        expect{ key_list_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_list_object.expired_and_non_expired_msg)
+      end
+    end
+
+    context "when the command is run" do
+      before do
+        key_list_object.config[:only_expired] = false
+        key_list_object.config[:only_non_expired] = false
+        key_list_object.config[:with_details] = false
+      end
+
+      it "calls Chef::Key with the proper list command and input" do
+        expect(Chef::Key).to receive(list_method).with(actor)
+        key_list_object.run
+      end
+
+      it "displays all the keys" do
+        expect(key_list_object).to receive(:display_info).with(/non-expired/).twice
+        expect(key_list_object).to receive(:display_info).with(/out-of-date/).once
+        key_list_object.run
+      end
+
+      context "when only_expired is called" do
+        before do
+          key_list_object.config[:only_expired] = true
+        end
+
+        it "excludes displaying non-expired keys" do
+          expect(key_list_object).to receive(:display_info).with(/non-expired/).exactly(0).times
+          key_list_object.run
+        end
+
+        it "displays the expired keys" do
+          expect(key_list_object).to receive(:display_info).with(/out-of-date/).once
+          key_list_object.run
+        end
+      end # when only_expired is called
+
+      context "when only_non_expired is called" do
+        before do
+          key_list_object.config[:only_non_expired] = true
+        end
+
+        it "excludes displaying expired keys" do
+          expect(key_list_object).to receive(:display_info).with(/out-of-date/).exactly(0).times
+          key_list_object.run
+        end
+
+        it "displays the non-expired keys" do
+          expect(key_list_object).to receive(:display_info).with(/non-expired/).twice
+          key_list_object.run
+        end
+      end # when only_expired is called
+
+      context "when with_details is false" do
+        before do
+          key_list_object.config[:with_details] = false
+        end
+
+        it "does not display the uri" do
+          expect(key_list_object).to receive(:display_info).with(/https/).exactly(0).times
+          key_list_object.run
+        end
+
+        it "does not display the expired status" do
+          expect(key_list_object).to receive(:display_info).with(/\(expired\)/).exactly(0).times
+          key_list_object.run
+        end
+      end # when with_details is false
+
+      context "when with_details is true" do
+        before do
+          key_list_object.config[:with_details] = true
+        end
+
+        it "displays the uri" do
+          expect(key_list_object).to receive(:display_info).with(/https/).exactly(3).times
+          key_list_object.run
+        end
+
+        it "displays the expired status" do
+          expect(key_list_object).to receive(:display_info).with(/\(expired\)/).once
+          key_list_object.run
+        end
+      end # when with_details is true
+
+    end # when the command is run
+
+  end # key list run command
+
+  context "when list_method is :list_by_user" do
+    it_should_behave_like "key list run command" do
+      let(:list_method) { :list_by_user }
+      let(:http_response) {
+        [
+          {"uri"=>"https://api.opscode.piab/users/charmander/keys/non-expired1", "name"=>"non-expired1", "expired"=>false},
+          {"uri"=>"https://api.opscode.piab/users/charmander/keys/non-expired2", "name"=>"non-expired2", "expired"=>false},
+          {"uri"=>"https://api.opscode.piab/users/mary/keys/out-of-date",        "name"=>"out-of-date", "expired"=>true}
+        ]
+      }
+    end
+  end
+
+  context "when list_method is :list_by_client" do
+    it_should_behave_like "key list run command" do
+      let(:list_method) { :list_by_client }
+      let(:http_response) {
+        [
+          {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired1", "name"=>"non-expired1", "expired"=>false},
+          {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired2", "name"=>"non-expired2", "expired"=>false},
+          {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/mary/keys/out-of-date",        "name"=>"out-of-date", "expired"=>true}
+        ]
+      }
+    end
+  end
+end
diff --git a/spec/unit/knife/key_show_spec.rb b/spec/unit/knife/key_show_spec.rb
new file mode 100644
index 0000000..5a0d839
--- /dev/null
+++ b/spec/unit/knife/key_show_spec.rb
@@ -0,0 +1,126 @@
+#
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/knife/user_key_show'
+require 'chef/knife/client_key_show'
+require 'chef/knife/key_show'
+require 'chef/key'
+
+describe "key show commands that inherit knife" do
+  shared_examples_for "a key show command" do
+    let(:stderr) { StringIO.new }
+    let(:params) { [] }
+    let(:service_object) { instance_double(Chef::Knife::KeyShow) }
+    let(:command) do
+      c = described_class.new([])
+      c.ui.config[:disable_editing] = true
+      allow(c.ui).to receive(:stderr).and_return(stderr)
+      allow(c.ui).to receive(:stdout).and_return(stderr)
+      allow(c).to receive(:show_usage)
+      c
+    end
+
+    context "after apply_params! is called with valid args" do
+      let(:params) { ["charmander", "charmander-key"] }
+      before do
+        command.apply_params!(params)
+      end
+
+      context "when the service object is called" do
+        it "creates a new instance of Chef::Knife::KeyShow with the correct args" do
+          expect(Chef::Knife::KeyShow).to receive(:new).
+                                           with("charmander-key", "charmander", command.load_method, command.ui).
+                                           and_return(service_object)
+          command.service_object
+        end
+      end # when the service object is called
+    end # after apply_params! is called with valid args
+  end # a key show command
+
+  describe Chef::Knife::UserKeyShow do
+    it_should_behave_like "a key show command"
+    # defined in key_helpers.rb
+    it_should_behave_like "a knife key command with a keyname as the second arg"
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyShow) }
+      let(:params) { ["charmander", "charmander-key"] }
+    end
+  end
+
+  describe Chef::Knife::ClientKeyShow do
+    it_should_behave_like "a key show command"
+    # defined in key_helpers.rb
+    it_should_behave_like "a knife key command with a keyname as the second arg"
+    it_should_behave_like "a knife key command" do
+      let(:service_object) { instance_double(Chef::Knife::KeyShow) }
+      let(:params) { ["charmander", "charmander-key"] }
+    end
+  end
+end
+
+describe Chef::Knife::KeyShow do
+  let(:actor) { "charmander" }
+  let(:keyname) { "charmander" }
+  let(:ui) { instance_double("Chef::Knife::UI") }
+  let(:expected_hash) {
+    {
+      actor_field_name => "charmander",
+      "name" => "charmander-key",
+      "public_key" => "some-public-key",
+      "expiration_date" => "infinity"
+    }
+  }
+
+  shared_examples_for "key show run command" do
+    let(:key_show_object) {
+      described_class.new(keyname, actor, load_method, ui)
+    }
+
+    before do
+      allow(key_show_object).to receive(:display_output)
+      allow(Chef::Key).to receive(load_method).and_return(Chef::Key.from_hash(expected_hash))
+    end
+
+    context "when the command is run" do
+      it "loads the key using the proper method and args" do
+        expect(Chef::Key).to receive(load_method).with(actor, keyname)
+        key_show_object.run
+      end
+
+      it "displays the key" do
+        expect(key_show_object).to receive(:display_output)
+        key_show_object.run
+      end
+    end
+  end
+
+  context "when load_method is :load_by_user" do
+    it_should_behave_like "key show run command" do
+      let(:load_method) { :load_by_user }
+      let(:actor_field_name) { 'user' }
+    end
+  end
+
+  context "when load_method is :load_by_client" do
+    it_should_behave_like "key show run command" do
+      let(:load_method) { :load_by_client }
+      let(:actor_field_name) { 'user' }
+    end
+  end
+end
diff --git a/spec/unit/knife/node_run_list_remove_spec.rb b/spec/unit/knife/node_run_list_remove_spec.rb
index ceceef7..a279a59 100644
--- a/spec/unit/knife/node_run_list_remove_spec.rb
+++ b/spec/unit/knife/node_run_list_remove_spec.rb
@@ -84,6 +84,23 @@ describe Chef::Knife::NodeRunListRemove do
         expect(@node.run_list).not_to include('role[monkey]')
         expect(@node.run_list).not_to include('recipe[duck::type]')
       end
+
+      it "should warn when the thing to remove is not in the runlist" do
+        @node.run_list << 'role[blah]'
+        @node.run_list << 'recipe[duck::type]'
+        @knife.name_args = [ 'adam', 'role[blork]' ]
+        expect(@knife.ui).to receive(:warn).with("role[blork] is not in the run list")
+        @knife.run
+      end
+
+      it "should warn even more when the thing to remove is not in the runlist and unqualified" do
+        @node.run_list << 'role[blah]'
+        @node.run_list << 'recipe[duck::type]'
+        @knife.name_args = [ 'adam', 'blork' ]
+        expect(@knife.ui).to receive(:warn).with("blork is not in the run list")
+        expect(@knife.ui).to receive(:warn).with(/did you forget recipe\[\] or role\[\]/)
+        @knife.run
+      end
     end
   end
 end
diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/osc_user_create_spec.rb
similarity index 89%
copy from spec/unit/knife/user_create_spec.rb
copy to spec/unit/knife/osc_user_create_spec.rb
index ad8821c..e4ed78f 100644
--- a/spec/unit/knife/user_create_spec.rb
+++ b/spec/unit/knife/osc_user_create_spec.rb
@@ -18,11 +18,16 @@
 
 require 'spec_helper'
 
-Chef::Knife::UserCreate.load_deps
+Chef::Knife::OscUserCreate.load_deps
 
-describe Chef::Knife::UserCreate do
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_create_spec.rb.
+
+describe Chef::Knife::OscUserCreate do
   before(:each) do
-    @knife = Chef::Knife::UserCreate.new
+    @knife = Chef::Knife::OscUserCreate.new
 
     @stdout = StringIO.new
     @stderr = StringIO.new
diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/osc_user_delete_spec.rb
similarity index 76%
copy from spec/unit/knife/user_delete_spec.rb
copy to spec/unit/knife/osc_user_delete_spec.rb
index 94cfbf3..4a3ec42 100644
--- a/spec/unit/knife/user_delete_spec.rb
+++ b/spec/unit/knife/osc_user_delete_spec.rb
@@ -18,10 +18,15 @@
 
 require 'spec_helper'
 
-describe Chef::Knife::UserDelete do
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_delete_spec.rb.
+
+describe Chef::Knife::OscUserDelete do
   before(:each) do
-    Chef::Knife::UserDelete.load_deps
-    @knife = Chef::Knife::UserDelete.new
+    Chef::Knife::OscUserDelete.load_deps
+    @knife = Chef::Knife::OscUserDelete.new
     @knife.name_args = [ 'my_user' ]
   end
 
diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/osc_user_edit_spec.rb
similarity index 81%
copy from spec/unit/knife/user_edit_spec.rb
copy to spec/unit/knife/osc_user_edit_spec.rb
index 0eb75cf..279f2e3 100644
--- a/spec/unit/knife/user_edit_spec.rb
+++ b/spec/unit/knife/osc_user_edit_spec.rb
@@ -18,13 +18,18 @@
 
 require 'spec_helper'
 
-describe Chef::Knife::UserEdit do
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_edit_spec.rb.
+
+describe Chef::Knife::OscUserEdit do
   before(:each) do
     @stderr = StringIO.new
     @stdout = StringIO.new
 
-    Chef::Knife::UserEdit.load_deps
-    @knife = Chef::Knife::UserEdit.new
+    Chef::Knife::OscUserEdit.load_deps
+    @knife = Chef::Knife::OscUserEdit.new
     allow(@knife.ui).to receive(:stderr).and_return(@stderr)
     allow(@knife.ui).to receive(:stdout).and_return(@stdout)
     @knife.name_args = [ 'my_user' ]
diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/osc_user_list_spec.rb
similarity index 71%
copy from spec/unit/knife/user_list_spec.rb
copy to spec/unit/knife/osc_user_list_spec.rb
index db097a5..f496a41 100644
--- a/spec/unit/knife/user_list_spec.rb
+++ b/spec/unit/knife/osc_user_list_spec.rb
@@ -18,10 +18,15 @@
 
 require 'spec_helper'
 
-describe Chef::Knife::UserList do
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_list_spec.rb.
+
+describe Chef::Knife::OscUserList do
   before(:each) do
-    Chef::Knife::UserList.load_deps
-    @knife = Chef::Knife::UserList.new
+    Chef::Knife::OscUserList.load_deps
+    @knife = Chef::Knife::OscUserList.new
   end
 
   it 'lists the users' do
diff --git a/spec/unit/knife/user_reregister_spec.rb b/spec/unit/knife/osc_user_reregister_spec.rb
similarity index 83%
copy from spec/unit/knife/user_reregister_spec.rb
copy to spec/unit/knife/osc_user_reregister_spec.rb
index 1268716..989eb18 100644
--- a/spec/unit/knife/user_reregister_spec.rb
+++ b/spec/unit/knife/osc_user_reregister_spec.rb
@@ -18,10 +18,15 @@
 
 require 'spec_helper'
 
-describe Chef::Knife::UserReregister do
+# DEPRECATION NOTE
+# This code only remains to support users still	operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_reregister_spec.rb.
+
+describe Chef::Knife::OscUserReregister do
   before(:each) do
-    Chef::Knife::UserReregister.load_deps
-    @knife = Chef::Knife::UserReregister.new
+    Chef::Knife::OscUserReregister.load_deps
+    @knife = Chef::Knife::OscUserReregister.new
     @knife.name_args = [ 'a_user' ]
     @user_mock = double('user_mock', :private_key => "private_key")
     allow(Chef::User).to receive(:load).and_return(@user_mock)
diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/osc_user_show_spec.rb
similarity index 79%
copy from spec/unit/knife/user_show_spec.rb
copy to spec/unit/knife/osc_user_show_spec.rb
index f97cbc3..18d2086 100644
--- a/spec/unit/knife/user_show_spec.rb
+++ b/spec/unit/knife/osc_user_show_spec.rb
@@ -18,10 +18,15 @@
 
 require 'spec_helper'
 
-describe Chef::Knife::UserShow do
+# DEPRECATION NOTE
+# This code only remains to support users still	operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur user_show_spec.rb.
+
+describe Chef::Knife::OscUserShow do
   before(:each) do
-    Chef::Knife::UserShow.load_deps
-    @knife = Chef::Knife::UserShow.new
+    Chef::Knife::OscUserShow.load_deps
+    @knife = Chef::Knife::OscUserShow.new
     @knife.name_args = [ 'my_user' ]
     @user_mock = double('user_mock')
   end
diff --git a/spec/unit/knife/ssh_spec.rb b/spec/unit/knife/ssh_spec.rb
index a838a21..723280b 100644
--- a/spec/unit/knife/ssh_spec.rb
+++ b/spec/unit/knife/ssh_spec.rb
@@ -28,10 +28,10 @@ describe Chef::Knife::Ssh do
   before do
     @knife = Chef::Knife::Ssh.new
     @knife.merge_configs
-    @knife.config[:attribute] = "fqdn"
     @node_foo = Chef::Node.new
     @node_foo.automatic_attrs[:fqdn] = "foo.example.org"
     @node_foo.automatic_attrs[:ipaddress] = "10.0.0.1"
+
     @node_bar = Chef::Node.new
     @node_bar.automatic_attrs[:fqdn] = "bar.example.org"
     @node_bar.automatic_attrs[:ipaddress] = "10.0.0.2"
@@ -52,15 +52,15 @@ describe Chef::Knife::Ssh do
       def self.should_return_specified_attributes
         it "returns an array of the attributes specified on the command line OR config file, if only one is set" do
           @knife.config[:attribute] = "ipaddress"
-          @knife.config[:attribute_from_cli] = "ipaddress"
+          Chef::Config[:knife][:ssh_attribute] = "ipaddress" # this value will be in the config file
           configure_query([@node_foo, @node_bar])
           expect(@knife).to receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
           @knife.configure_session
         end
 
         it "returns an array of the attributes specified on the command line even when a config value is set" do
-          @knife.config[:attribute] = "config_file" # this value will be the config file
-          @knife.config[:attribute_from_cli] = "ipaddress" # this is the value of the command line via #configure_attribute
+          Chef::Config[:knife][:ssh_attribute] = "config_file" # this value will be in the config file
+          @knife.config[:attribute] = "ipaddress" # this is the value of the command line via #configure_attribute
           configure_query([@node_foo, @node_bar])
           expect(@knife).to receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
           @knife.configure_session
@@ -83,7 +83,6 @@ describe Chef::Knife::Ssh do
           @node_foo.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-1.compute-1.amazonaws.com"
           @node_bar.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-2.compute-1.amazonaws.com"
         end
-
         it "returns an array of cloud public hostnames" do
           configure_query([@node_foo, @node_bar])
           expect(@knife).to receive(:session_from_list).with([
@@ -150,42 +149,40 @@ describe Chef::Knife::Ssh do
     end
   end
 
-  describe "#configure_attribute" do
+  describe "#get_ssh_attribute" do
+    # Order of precedence for ssh target
+    # 1) command line attribute
+    # 2) configuration file
+    # 3) cloud attribute
+    # 4) fqdn
     before do
       Chef::Config[:knife][:ssh_attribute] = nil
       @knife.config[:attribute] = nil
+      @node_foo.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-1.compute-1.amazonaws.com"
+      @node_bar.automatic_attrs[:cloud][:public_hostname] = ''
     end
 
     it "should return fqdn by default" do
-      @knife.configure_attribute
-      expect(@knife.config[:attribute]).to eq("fqdn")
+      expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("fqdn")
     end
 
-    it "should return the value set in the configuration file" do
-      Chef::Config[:knife][:ssh_attribute] = "config_file"
-      @knife.configure_attribute
-      expect(@knife.config[:attribute]).to eq("config_file")
+    it "should return cloud.public_hostname attribute if available" do
+      expect(@knife.get_ssh_attribute(@node_foo)).to eq("cloud.public_hostname")
     end
 
-    it "should return the value set on the command line" do
+    it "should favor to attribute_from_cli over config file and cloud" do 
       @knife.config[:attribute] = "command_line"
-      @knife.configure_attribute
-      expect(@knife.config[:attribute]).to eq("command_line")
+      Chef::Config[:knife][:ssh_attribute] = "config_file"
+      expect( @knife.get_ssh_attribute(@node_foo)).to eq("command_line")
     end
 
-    it "should set attribute_from_cli to the value of attribute from the command line" do
-      @knife.config[:attribute] = "command_line"
-      @knife.configure_attribute
-      expect(@knife.config[:attribute]).to eq("command_line")
-      expect(@knife.config[:attribute_from_cli]).to eq("command_line")
+    it "should favor config file over cloud and default" do 
+      Chef::Config[:knife][:ssh_attribute] = "config_file"
+      expect( @knife.get_ssh_attribute(@node_foo)).to eq("config_file")
     end
 
-    it "should prefer the command line over the config file for the value of attribute_from_cli" do
-      Chef::Config[:knife][:ssh_attribute] = "config_file"
-      @knife.config[:attribute] = "command_line"
-      @knife.configure_attribute
-      expect(@knife.config[:attribute]).to eq("command_line")
-      expect(@knife.config[:attribute_from_cli]).to eq("command_line")
+    it "should return fqdn if cloud.hostname is empty" do
+          expect( @knife.get_ssh_attribute(@node_bar)).to eq("fqdn")
     end
   end
 
diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb
index 8eda555..feeb85c 100644
--- a/spec/unit/knife/ssl_check_spec.rb
+++ b/spec/unit/knife/ssl_check_spec.rb
@@ -145,7 +145,7 @@ E
     let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) }
 
     before do
-      expect(TCPSocket).to receive(:new).with("foo.example.com", 8443).and_return(tcp_socket)
+      expect(ssl_check).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket)
       expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_check.verify_peer_ssl_context).and_return(ssl_socket)
     end
 
@@ -163,6 +163,7 @@ E
         expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs (no warn)
         expect(ssl_socket).to receive(:connect) # no error
         expect(ssl_socket).to receive(:post_connection_check).with("foo.example.com") # no error
+        expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error
       end
 
       it "prints a success message" do
@@ -180,9 +181,9 @@ E
       let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
 
       before do
-        trap(:INT, "DEFAULT")
+        @old_signal = trap(:INT, "DEFAULT")
 
-        expect(TCPSocket).to receive(:new).
+        expect(ssl_check).to receive(:proxified_socket).
           with("foo.example.com", 8443).
           and_return(tcp_socket_for_debug)
         expect(OpenSSL::SSL::SSLSocket).to receive(:new).
@@ -190,6 +191,10 @@ E
           and_return(ssl_socket_for_debug)
       end
 
+      after do
+        trap(:INT, @old_signal)
+      end
+
       context "when the certificate's CN does not match the hostname" do
         before do
           expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs
@@ -197,6 +202,7 @@ E
           expect(ssl_socket).to receive(:post_connection_check).
             with("foo.example.com").
             and_raise(OpenSSL::SSL::SSLError)
+          expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error
           expect(ssl_socket_for_debug).to receive(:connect)
           expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt)
         end
@@ -215,6 +221,8 @@ E
           expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs
           expect(ssl_socket).to receive(:connect).
             and_raise(OpenSSL::SSL::SSLError)
+          expect(ssl_socket).to receive(:hostname=).
+            with("foo.example.com") # no error
           expect(ssl_socket_for_debug).to receive(:connect)
           expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt)
         end
diff --git a/spec/unit/knife/ssl_fetch_spec.rb b/spec/unit/knife/ssl_fetch_spec.rb
index cd0e423..5982ed9 100644
--- a/spec/unit/knife/ssl_fetch_spec.rb
+++ b/spec/unit/knife/ssl_fetch_spec.rb
@@ -139,7 +139,7 @@ E
     context "when the TLS connection is successful" do
 
       before do
-        expect(TCPSocket).to receive(:new).with("foo.example.com", 8443).and_return(tcp_socket)
+        expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket)
         expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
         expect(ssl_socket).to receive(:connect)
         expect(ssl_socket).to receive(:peer_cert_chain).and_return([self_signed_crt])
@@ -161,7 +161,7 @@ E
       let(:unknown_protocol_error) { OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol") }
 
       before do
-        expect(TCPSocket).to receive(:new).with("foo.example.com", 80).and_return(tcp_socket)
+        expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 80).and_return(tcp_socket)
         expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
         expect(ssl_socket).to receive(:connect).and_raise(unknown_protocol_error)
 
diff --git a/spec/unit/knife/status_spec.rb b/spec/unit/knife/status_spec.rb
index ee44f3b..11728a6 100644
--- a/spec/unit/knife/status_spec.rb
+++ b/spec/unit/knife/status_spec.rb
@@ -44,9 +44,9 @@ describe Chef::Knife::Status do
       @knife.run
     end
 
-    it "should filter healthy nodes" do
-      @knife.config[:hide_healthy] = true
-      expect(@query).to receive(:search).with(:node, "NOT ohai_time:[1428569820 TO 1428573420]", opts)
+    it "should filter by nodes older than some mins" do
+      @knife.config[:hide_by_mins] = 59
+      expect(@query).to receive(:search).with(:node, "NOT ohai_time:[1428569880 TO 1428573420]", opts)
       @knife.run
     end
 
@@ -56,10 +56,10 @@ describe Chef::Knife::Status do
       @knife.run
     end
 
-    it "should filter by environment and health" do
+    it "should filter by environment and nodes older than some mins" do
       @knife.config[:environment] = "production"
-      @knife.config[:hide_healthy] = true
-      expect(@query).to receive(:search).with(:node, "chef_environment:production NOT ohai_time:[1428569820 TO 1428573420]", opts)
+      @knife.config[:hide_by_mins] = 59
+      expect(@query).to receive(:search).with(:node, "chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts)
       @knife.run
     end
 
@@ -79,22 +79,22 @@ describe Chef::Knife::Status do
         @knife.run
       end
 
-      it "should filter healthy nodes" do
-        @knife.config[:hide_healthy] = true
-        expect(@query).to receive(:search).with(:node, "name:my_custom_name NOT ohai_time:[1428569820 TO 1428573420]", opts)
+      it "should filter by nodes older than some mins with nodename specified" do
+        @knife.config[:hide_by_mins] = 59
+        expect(@query).to receive(:search).with(:node, "name:my_custom_name NOT ohai_time:[1428569880 TO 1428573420]", opts)
         @knife.run
       end
 
-      it "should filter by environment" do
+      it "should filter by environment with nodename specified" do
         @knife.config[:environment] = "production"
         expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production", opts)
         @knife.run
       end
 
-      it "should filter by environment and health" do
+      it "should filter by environment and nodes older than some mins with nodename specified" do
         @knife.config[:environment] = "production"
-        @knife.config[:hide_healthy] = true
-        expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production NOT ohai_time:[1428569820 TO 1428573420]", opts)
+        @knife.config[:hide_by_mins] = 59
+        expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts)
         @knife.run
       end
     end
diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb
index ad8821c..fa5c832 100644
--- a/spec/unit/knife/user_create_spec.rb
+++ b/spec/unit/knife/user_create_spec.rb
@@ -1,6 +1,7 @@
 #
-# Author:: Steven Danna (<steve at opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Author:: Steven Danna (<steve at chef.io>)
+# Author:: Tyler Cloke (<tyler at chef.io>)
+# Copyright:: Copyright (c) 2012, 2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,68 +22,193 @@ require 'spec_helper'
 Chef::Knife::UserCreate.load_deps
 
 describe Chef::Knife::UserCreate do
+  let(:knife) { Chef::Knife::UserCreate.new }
+
+  let(:stderr) {
+    StringIO.new
+  }
+
+  let(:stdout) {
+    StringIO.new
+  }
+
   before(:each) do
-    @knife = Chef::Knife::UserCreate.new
-
-    @stdout = StringIO.new
-    @stderr = StringIO.new
-    allow(@knife.ui).to receive(:stdout).and_return(@stdout)
-    allow(@knife.ui).to receive(:stderr).and_return(@stderr)
-
-    @knife.name_args = [ 'a_user' ]
-    @knife.config[:user_password] = "foobar"
-    @user = Chef::User.new
-    @user.name "a_user"
-    @user_with_private_key = Chef::User.new
-    @user_with_private_key.name "a_user"
-    @user_with_private_key.private_key 'private_key'
-    allow(@user).to receive(:create).and_return(@user_with_private_key)
-    allow(Chef::User).to receive(:new).and_return(@user)
-    allow(Chef::User).to receive(:from_hash).and_return(@user)
-    allow(@knife).to receive(:edit_data).and_return(@user.to_hash)
+    allow(knife.ui).to receive(:stdout).and_return(stdout)
+    allow(knife.ui).to receive(:stderr).and_return(stderr)
+    allow(knife.ui).to receive(:warn)
   end
 
-  it "creates a new user" do
-    expect(Chef::User).to receive(:new).and_return(@user)
-    expect(@user).to receive(:create)
-    @knife.run
-    expect(@stderr.string).to match /created user.+a_user/i
-  end
+  # delete this once OSC11 support is gone
+  context "when only one name_arg is passed" do
+    before do
+      knife.name_args = ['some_user']
+      allow(knife).to receive(:run_osc_11_user_create).and_raise(SystemExit)
+    end
+
+    it "displays the osc warning" do
+      expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
+
+    it "calls knife osc_user create" do
+      expect(knife).to receive(:run_osc_11_user_create)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
 
-  it "sets the password" do
-    @knife.config[:user_password] = "a_password"
-    expect(@user).to receive(:password).with("a_password")
-    @knife.run
   end
 
-  it "exits with an error if password is blank" do
-    @knife.config[:user_password] = ''
-    expect { @knife.run }.to raise_error SystemExit
-    expect(@stderr.string).to match /You must specify a non-blank password/
+  context "when USERNAME isn't specified" do
+    # from spec/support/shared/unit/knife_shared.rb
+    it_should_behave_like "mandatory field missing" do
+      let(:name_args) { [] }
+      let(:fieldname) { 'username' }
+    end
   end
 
-  it "sets the user name" do
-    expect(@user).to receive(:name).with("a_user")
-    @knife.run
+  # uncomment once OSC11 support is gone,
+  # pending doesn't work for shared_examples_for by default
+  #
+  # context "when DISPLAY_NAME isn't specified" do
+  #   # from spec/support/shared/unit/knife_shared.rb
+  #   it_should_behave_like "mandatory field missing" do
+  #     let(:name_args) { ['some_user'] }
+  #     let(:fieldname) { 'display name' }
+  #   end
+  # end
+
+  context "when FIRST_NAME isn't specified" do
+    # from spec/support/shared/unit/knife_shared.rb
+    it_should_behave_like "mandatory field missing" do
+      let(:name_args) { ['some_user', 'some_display_name'] }
+      let(:fieldname) { 'first name' }
+    end
   end
 
-  it "sets the public key if given" do
-    @knife.config[:user_key] = "/a/filename"
-    allow(File).to receive(:read).with(File.expand_path("/a/filename")).and_return("a_key")
-    expect(@user).to receive(:public_key).with("a_key")
-    @knife.run
+  context "when LAST_NAME isn't specified" do
+    # from spec/support/shared/unit/knife_shared.rb
+    it_should_behave_like "mandatory field missing" do
+      let(:name_args) { ['some_user', 'some_display_name', 'some_first_name'] }
+      let(:fieldname) { 'last name' }
+    end
   end
 
-  it "allows you to edit the data" do
-    expect(@knife).to receive(:edit_data).with(@user)
-    @knife.run
+  context "when EMAIL isn't specified" do
+    # from spec/support/shared/unit/knife_shared.rb
+    it_should_behave_like "mandatory field missing" do
+      let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name'] }
+      let(:fieldname) { 'email' }
+    end
   end
 
-  it "writes the private key to a file when --file is specified" do
-    @knife.config[:file] = "/tmp/a_file"
-    filehandle = double("filehandle")
-    expect(filehandle).to receive(:print).with('private_key')
-    expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle)
-    @knife.run
+  context "when PASSWORD isn't specified" do
+    # from spec/support/shared/unit/knife_shared.rb
+    it_should_behave_like "mandatory field missing" do
+      let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email'] }
+      let(:fieldname) { 'password' }
+    end
   end
+
+  context "when all mandatory fields are validly specified" do
+    before do
+      knife.name_args = ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email', 'some_password']
+      allow(knife).to receive(:edit_data).and_return(knife.user.to_hash)
+      allow(knife).to receive(:create_user_from_hash).and_return(knife.user)
+    end
+
+    before(:each) do
+      # reset the user field every run
+      knife.user_field = nil
+    end
+
+    it "sets all the mandatory fields" do
+      knife.run
+      expect(knife.user.username).to eq('some_user')
+      expect(knife.user.display_name).to eq('some_display_name')
+      expect(knife.user.first_name).to eq('some_first_name')
+      expect(knife.user.last_name).to eq('some_last_name')
+      expect(knife.user.email).to eq('some_email')
+      expect(knife.user.password).to eq('some_password')
+    end
+
+    context "when user_key and prevent_keygen are passed" do
+      before do
+        knife.config[:user_key] = "some_key"
+        knife.config[:prevent_keygen] = true
+      end
+      it "prints the usage" do
+        expect(knife).to receive(:show_usage)
+        expect { knife.run }.to raise_error(SystemExit)
+      end
+
+      it "prints a relevant error message" do
+        expect { knife.run }.to raise_error(SystemExit)
+        expect(stderr.string).to match /You cannot pass --user-key and --prevent-keygen/
+      end
+    end
+
+    context "when --prevent-keygen is passed" do
+      before do
+        knife.config[:prevent_keygen] = true
+      end
+
+      it "does not set user.create_key" do
+        knife.run
+        expect(knife.user.create_key).to be_falsey
+      end
+    end
+
+    context "when --prevent-keygen is not passed" do
+      it "sets user.create_key to true" do
+        knife.run
+        expect(knife.user.create_key).to be_truthy
+      end
+    end
+
+    context "when --user-key is passed" do
+      before do
+        knife.config[:user_key] = 'some_key'
+        allow(File).to receive(:read).and_return('some_key')
+        allow(File).to receive(:expand_path)
+      end
+
+      it "sets user.public_key" do
+        knife.run
+        expect(knife.user.public_key).to eq('some_key')
+      end
+    end
+
+    context "when --user-key is not passed" do
+      it "does not set user.public_key" do
+        knife.run
+        expect(knife.user.public_key).to be_nil
+      end
+    end
+
+    context "when a private_key is returned" do
+      before do
+        allow(knife).to receive(:create_user_from_hash).and_return(Chef::UserV1.from_hash(knife.user.to_hash.merge({"private_key" => "some_private_key"})))
+      end
+
+      context "when --file is passed" do
+        before do
+          knife.config[:file] = '/some/path'
+        end
+
+        it "creates a new file of the path passed" do
+          filehandle = double('filehandle')
+          expect(filehandle).to receive(:print).with('some_private_key')
+          expect(File).to receive(:open).with('/some/path', 'w').and_yield(filehandle)
+          knife.run
+        end
+      end
+
+      context "when --file is not passed" do
+        it "prints the private key to stdout" do
+          expect(knife.ui).to receive(:msg).with('some_private_key')
+          knife.run
+        end
+      end
+    end
+
+  end # when all mandatory fields are validly specified
 end
diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/user_delete_spec.rb
index 94cfbf3..a241606 100644
--- a/spec/unit/knife/user_delete_spec.rb
+++ b/spec/unit/knife/user_delete_spec.rb
@@ -19,21 +19,47 @@
 require 'spec_helper'
 
 describe Chef::Knife::UserDelete do
+  let(:knife) { Chef::Knife::UserDelete.new }
+  let(:user) { double('user_object') }
+  let(:stdout) { StringIO.new }
+
   before(:each) do
     Chef::Knife::UserDelete.load_deps
-    @knife = Chef::Knife::UserDelete.new
-    @knife.name_args = [ 'my_user' ]
+    knife.name_args = [ 'my_user' ]
+    allow(Chef::UserV1).to receive(:load).and_return(user)
+    allow(user).to receive(:username).and_return('my_user')
+    allow(knife.ui).to receive(:stderr).and_return(stdout)
+    allow(knife.ui).to receive(:stdout).and_return(stdout)
+  end
+
+  # delete this once OSC11 support is gone
+  context "when the username field is not supported by the server" do
+    before do
+      allow(knife).to receive(:run_osc_11_user_delete).and_raise(SystemExit)
+      allow(user).to receive(:username).and_return(nil)
+    end
+
+    it "displays the osc warning" do
+      expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
+
+    it "forwards the command to knife osc_user edit" do
+      expect(knife).to receive(:run_osc_11_user_delete)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
   end
 
   it 'deletes the user' do
-    expect(@knife).to receive(:delete_object).with(Chef::User, 'my_user')
-    @knife.run
+    #expect(knife).to receive(:delete_object).with(Chef::UserV1, 'my_user')
+    expect(knife).to receive(:delete_object).with('my_user')
+    knife.run
   end
 
   it 'prints usage and exits when a user name is not provided' do
-    @knife.name_args = []
-    expect(@knife).to receive(:show_usage)
-    expect(@knife.ui).to receive(:fatal)
-    expect { @knife.run }.to raise_error(SystemExit)
+    knife.name_args = []
+    expect(knife).to receive(:show_usage)
+    expect(knife.ui).to receive(:fatal)
+    expect { knife.run }.to raise_error(SystemExit)
   end
 end
diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/user_edit_spec.rb
index 0eb75cf..a21d982 100644
--- a/spec/unit/knife/user_edit_spec.rb
+++ b/spec/unit/knife/user_edit_spec.rb
@@ -19,29 +19,48 @@
 require 'spec_helper'
 
 describe Chef::Knife::UserEdit do
+  let(:knife) { Chef::Knife::UserEdit.new }
+
   before(:each) do
     @stderr = StringIO.new
     @stdout = StringIO.new
 
     Chef::Knife::UserEdit.load_deps
-    @knife = Chef::Knife::UserEdit.new
-    allow(@knife.ui).to receive(:stderr).and_return(@stderr)
-    allow(@knife.ui).to receive(:stdout).and_return(@stdout)
-    @knife.name_args = [ 'my_user' ]
-    @knife.config[:disable_editing] = true
+    allow(knife.ui).to receive(:stderr).and_return(@stderr)
+    allow(knife.ui).to receive(:stdout).and_return(@stdout)
+    knife.name_args = [ 'my_user' ]
+    knife.config[:disable_editing] = true
+  end
+
+  # delete this once OSC11 support is gone
+  context "when the username field is not supported by the server" do
+    before do
+      allow(knife).to receive(:run_osc_11_user_edit).and_raise(SystemExit)
+      allow(Chef::UserV1).to receive(:load).and_return({"username" => nil})
+    end
+
+    it "displays the osc warning" do
+      expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
+
+    it "forwards the command to knife osc_user edit" do
+      expect(knife).to receive(:run_osc_11_user_edit)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
   end
 
   it 'loads and edits the user' do
-    data = { :name => "my_user" }
-    allow(Chef::User).to receive(:load).with("my_user").and_return(data)
-    expect(@knife).to receive(:edit_data).with(data).and_return(data)
-    @knife.run
+    data = { "username" => "my_user" }
+    allow(Chef::UserV1).to receive(:load).with("my_user").and_return(data)
+    expect(knife).to receive(:edit_data).with(data).and_return(data)
+    knife.run
   end
 
   it 'prints usage and exits when a user name is not provided' do
-    @knife.name_args = []
-    expect(@knife).to receive(:show_usage)
-    expect(@knife.ui).to receive(:fatal)
-    expect { @knife.run }.to raise_error(SystemExit)
+    knife.name_args = []
+    expect(knife).to receive(:show_usage)
+    expect(knife.ui).to receive(:fatal)
+    expect { knife.run }.to raise_error(SystemExit)
   end
 end
diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb
index db097a5..fa2bac4 100644
--- a/spec/unit/knife/user_list_spec.rb
+++ b/spec/unit/knife/user_list_spec.rb
@@ -19,14 +19,18 @@
 require 'spec_helper'
 
 describe Chef::Knife::UserList do
+  let(:knife) { Chef::Knife::UserList.new }
+  let(:stdout) { StringIO.new }
+
   before(:each) do
     Chef::Knife::UserList.load_deps
-    @knife = Chef::Knife::UserList.new
+    allow(knife.ui).to receive(:stderr).and_return(stdout)
+    allow(knife.ui).to receive(:stdout).and_return(stdout)
   end
 
   it 'lists the users' do
-    expect(Chef::User).to receive(:list)
-    expect(@knife).to receive(:format_list_for_display)
-    @knife.run
+    expect(Chef::UserV1).to receive(:list)
+    expect(knife).to receive(:format_list_for_display)
+    knife.run
   end
 end
diff --git a/spec/unit/knife/user_reregister_spec.rb b/spec/unit/knife/user_reregister_spec.rb
index 1268716..89aa672 100644
--- a/spec/unit/knife/user_reregister_spec.rb
+++ b/spec/unit/knife/user_reregister_spec.rb
@@ -19,35 +19,56 @@
 require 'spec_helper'
 
 describe Chef::Knife::UserReregister do
-  before(:each) do
+  let(:knife) { Chef::Knife::UserReregister.new }
+  let(:user_mock) { double('user_mock', :private_key => "private_key") }
+  let(:stdout) { StringIO.new }
+
+  before do
     Chef::Knife::UserReregister.load_deps
-    @knife = Chef::Knife::UserReregister.new
-    @knife.name_args = [ 'a_user' ]
-    @user_mock = double('user_mock', :private_key => "private_key")
-    allow(Chef::User).to receive(:load).and_return(@user_mock)
-    @stdout = StringIO.new
-    allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+    knife.name_args = [ 'a_user' ]
+    allow(Chef::UserV1).to receive(:load).and_return(user_mock)
+    allow(knife.ui).to receive(:stdout).and_return(stdout)
+    allow(knife.ui).to receive(:stderr).and_return(stdout)
+    allow(user_mock).to receive(:username).and_return('a_user')
+  end
+
+  # delete this once OSC11 support is gone
+  context "when the username field is not supported by the server" do
+    before do
+      allow(knife).to receive(:run_osc_11_user_reregister).and_raise(SystemExit)
+      allow(user_mock).to receive(:username).and_return(nil)
+    end
+
+    it "displays the osc warning" do
+      expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
+
+    it "forwards the command to knife osc_user edit" do
+      expect(knife).to receive(:run_osc_11_user_reregister)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
   end
 
   it 'prints usage and exits when a user name is not provided' do
-    @knife.name_args = []
-    expect(@knife).to receive(:show_usage)
-    expect(@knife.ui).to receive(:fatal)
-    expect { @knife.run }.to raise_error(SystemExit)
+    knife.name_args = []
+    expect(knife).to receive(:show_usage)
+    expect(knife.ui).to receive(:fatal)
+    expect { knife.run }.to raise_error(SystemExit)
   end
 
   it 'reregisters the user and prints the key' do
-    expect(@user_mock).to receive(:reregister).and_return(@user_mock)
-    @knife.run
-    expect(@stdout.string).to match( /private_key/ )
+    expect(user_mock).to receive(:reregister).and_return(user_mock)
+    knife.run
+    expect(stdout.string).to match( /private_key/ )
   end
 
   it 'writes the private key to a file when --file is specified' do
-    expect(@user_mock).to receive(:reregister).and_return(@user_mock)
-    @knife.config[:file] = '/tmp/a_file'
+    expect(user_mock).to receive(:reregister).and_return(user_mock)
+    knife.config[:file] = '/tmp/a_file'
     filehandle = StringIO.new
     expect(File).to receive(:open).with('/tmp/a_file', 'w').and_yield(filehandle)
-    @knife.run
+    knife.run
     expect(filehandle.string).to eq("private_key")
   end
 end
diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb
index f97cbc3..7c39e42 100644
--- a/spec/unit/knife/user_show_spec.rb
+++ b/spec/unit/knife/user_show_spec.rb
@@ -19,23 +19,47 @@
 require 'spec_helper'
 
 describe Chef::Knife::UserShow do
-  before(:each) do
+  let(:knife) { Chef::Knife::UserShow.new }
+  let(:user_mock) { double('user_mock') }
+  let(:stdout) { StringIO.new }
+
+  before do
     Chef::Knife::UserShow.load_deps
-    @knife = Chef::Knife::UserShow.new
-    @knife.name_args = [ 'my_user' ]
-    @user_mock = double('user_mock')
+    knife.name_args = [ 'my_user' ]
+    allow(user_mock).to receive(:username).and_return('my_user')
+    allow(knife.ui).to receive(:stderr).and_return(stdout)
+    allow(knife.ui).to receive(:stdout).and_return(stdout)
+  end
+
+  # delete this once OSC11 support is gone
+  context "when the username field is not supported by the server" do
+    before do
+      allow(knife).to receive(:run_osc_11_user_show).and_raise(SystemExit)
+      allow(Chef::UserV1).to receive(:load).with('my_user').and_return(user_mock)
+      allow(user_mock).to receive(:username).and_return(nil)
+    end
+
+    it "displays the osc warning" do
+      expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
+
+    it "forwards the command to knife osc_user edit" do
+      expect(knife).to receive(:run_osc_11_user_show)
+      expect{ knife.run }.to raise_error(SystemExit)
+    end
   end
 
   it 'loads and displays the user' do
-    expect(Chef::User).to receive(:load).with('my_user').and_return(@user_mock)
-    expect(@knife).to receive(:format_for_display).with(@user_mock)
-    @knife.run
+    expect(Chef::UserV1).to receive(:load).with('my_user').and_return(user_mock)
+    expect(knife).to receive(:format_for_display).with(user_mock)
+    knife.run
   end
 
   it 'prints usage and exits when a user name is not provided' do
-    @knife.name_args = []
-    expect(@knife).to receive(:show_usage)
-    expect(@knife.ui).to receive(:fatal)
-    expect { @knife.run }.to raise_error(SystemExit)
+    knife.name_args = []
+    expect(knife).to receive(:show_usage)
+    expect(knife.ui).to receive(:fatal)
+    expect { knife.run }.to raise_error(SystemExit)
   end
 end
diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb
index b748232..5ab8e84 100644
--- a/spec/unit/knife_spec.rb
+++ b/spec/unit/knife_spec.rb
@@ -30,11 +30,23 @@ describe Chef::Knife do
 
   let(:knife) { Chef::Knife.new }
 
+  let(:config_location) { File.expand_path("~/.chef/config.rb") }
+
+  let(:config_loader) do
+    instance_double("WorkstationConfigLoader",
+                    load: nil, no_config_found?: false,
+                    config_location: config_location,
+                    :chef_config_dir => "/etc/chef")
+  end
+
   before(:each) do
     Chef::Log.logger = Logger.new(StringIO.new)
 
     Chef::Config[:node_name]  = "webmonkey.example.com"
 
+    allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader)
+    allow(config_loader).to receive(:explicit_config_file=)
+
     # Prevent gratuitous code reloading:
     allow(Chef::Knife).to receive(:load_commands)
     allow(knife.ui).to receive(:puts)
@@ -51,6 +63,12 @@ describe Chef::Knife do
     Chef::Knife.reset_config_loader!
   end
 
+  it "does not reset Chef::Config[:verbosity to nil if config[:verbosity] is nil" do
+    Chef::Config[:verbosity] = 2
+    Chef::Knife.new
+    expect(Chef::Config[:verbosity]).to eq(2)
+  end
+
   describe "after loading a subcommand" do
     before do
       Chef::Knife.reset_subcommands!
@@ -108,6 +126,14 @@ describe Chef::Knife do
       expect(Chef::Knife.subcommands["super_awesome_command"]).to eq(SuperAwesomeCommand)
     end
 
+    it "records the location of ChefFS-based commands correctly" do
+      class AwesomeCheffsCommand < Chef::ChefFS::Knife
+      end
+
+      Chef::Knife.load_commands
+      expect(Chef::Knife.subcommand_files["awesome_cheffs_command"]).to eq([__FILE__])
+    end
+
     it "guesses a category from a given ARGV" do
       Chef::Knife.subcommands_by_category["cookbook"] << :cookbook
       Chef::Knife.subcommands_by_category["cookbook site"] << :cookbook_site
@@ -130,7 +156,8 @@ describe Chef::Knife do
                     "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
                     'X-Chef-Version' => Chef::VERSION,
                     "Host"=>"api.opscode.piab",
-                    "X-REMOTE-REQUEST-ID"=>request_id}}
+                    "X-REMOTE-REQUEST-ID"=>request_id,
+                    'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}}
 
     let(:request_id) {"1234"}
 
@@ -251,6 +278,18 @@ describe Chef::Knife do
                                         :default => "default-value")
       end
 
+      it "sets the default log_location to STDERR for Chef::Log warnings" do
+        knife_command = KnifeSpecs::TestYourself.new([])
+        knife_command.configure_chef
+        expect(Chef::Config[:log_location]).to eq(STDERR)
+      end
+
+      it "sets the default log_level to warn so we can issue Chef::Log.warn" do
+        knife_command = KnifeSpecs::TestYourself.new([])
+        knife_command.configure_chef
+        expect(Chef::Config[:log_level]).to eql(:warn)
+      end
+
       it "prefers the default value if no config or command line value is present" do
         knife_command = KnifeSpecs::TestYourself.new([]) #empty argv
         knife_command.configure_chef
@@ -276,7 +315,7 @@ describe Chef::Knife do
         expect(Chef::Config[:listen]).to be(false)
       end
 
-      context "verbosity is greater than zero" do
+      context "verbosity is one" do
         let(:fake_config) { "/does/not/exist/knife.rb" }
 
         before do
@@ -294,6 +333,13 @@ describe Chef::Knife do
           knife.configure_chef
         end
       end
+
+      it "does not humanize the exception if Chef::Config[:verbosity] is two" do
+        Chef::Config[:verbosity] = 2
+        allow(knife).to receive(:run).and_raise(Exception)
+        expect(knife).not_to receive(:humanize_exception)
+        expect { knife.run_with_pretty_exceptions }.to raise_error(Exception)
+      end
     end
   end
 
@@ -374,6 +420,22 @@ describe Chef::Knife do
       expect(stderr.string).to match(%r[Response: nothing to see here])
     end
 
+    it "formats 406s (non-supported API version error) nicely" do
+      response = Net::HTTPNotAcceptable.new("1.1", "406", "Not Acceptable")
+      response.instance_variable_set(:@read, true) # I hate you, net/http.
+
+      # set the header
+      response["x-ops-server-api-version"] = Chef::JSONCompat.to_json(:min_version => "0", :max_version => "1", :request_version => "10000000")
+
+      allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone"))
+      allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("406 Not Acceptable", response))
+
+      knife.run_with_pretty_exceptions
+      expect(stderr.string).to include('The request that Knife sent was using API version 10000000')
+      expect(stderr.string).to include('The Chef server you sent the request to supports a min API verson of 0 and a max API version of 1')
+      expect(stderr.string).to include('Please either update your Chef client or server to be a compatible set')
+    end
+
     it "formats 500s nicely" do
       response = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error")
       response.instance_variable_set(:@read, true) # I hate you, net/http.
diff --git a/spec/unit/log/syslog_spec.rb b/spec/unit/log/syslog_spec.rb
new file mode 100644
index 0000000..3db90e5
--- /dev/null
+++ b/spec/unit/log/syslog_spec.rb
@@ -0,0 +1,53 @@
+#
+# Author:: SAWANOBORI Yukihiko (<sawanoboriyu at higanworks.com>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef'
+
+describe "Chef::Log::Syslog", :unix_only => true do
+  let(:syslog) { Chef::Log::Syslog.new }
+  let(:app) { Chef::Application.new }
+
+  before do
+    Chef::Log.init(MonoLogger.new(syslog))
+    @old_log_level = Chef::Log.level
+    Chef::Log.level = :info
+    @old_loggers = Chef::Log.loggers
+    Chef::Log.use_log_devices([syslog])
+  end
+
+  after do
+    Chef::Log.level = @old_log_level
+    Chef::Log.use_log_devices(@old_loggers)
+  end
+
+  it "should send message with severity info to syslog." do
+    expect(syslog).to receive(:info).with("*** Chef 12.4.0.dev.0 ***")
+    Chef::Log.info("*** Chef 12.4.0.dev.0 ***")
+  end
+
+  it "should send message with severity warning to syslog." do
+    expect(syslog).to receive(:warn).with("No config file found or specified on command line, using command line options.")
+    Chef::Log.warn("No config file found or specified on command line, using command line options.")
+  end
+
+  it "should fallback into send message with severity info to syslog when wrong format." do
+    expect(syslog).to receive(:info).with("chef message")
+    syslog.write("chef message")
+  end
+end
diff --git a/spec/unit/log/winevt_spec.rb b/spec/unit/log/winevt_spec.rb
new file mode 100644
index 0000000..867ef55
--- /dev/null
+++ b/spec/unit/log/winevt_spec.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Jay Mundrawala (jdm at chef.io)
+# Author:: SAWANOBORI Yukihiko (<sawanoboriyu at higanworks.com>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+describe Chef::Log::WinEvt do
+  let(:evtlog) { instance_double("Win32::EventLog")}
+  let(:winevt) { Chef::Log::WinEvt.new(evtlog) }
+  let(:app) { Chef::Application.new }
+
+  before do
+
+    Chef::Log.init(MonoLogger.new(winevt))
+    @old_log_level = Chef::Log.level
+    Chef::Log.level = :info
+    @old_loggers = Chef::Log.loggers
+    Chef::Log.use_log_devices([winevt])
+  end
+
+  after do
+    Chef::Log.level = @old_log_level
+    Chef::Log.use_log_devices(@old_loggers)
+  end
+
+  it "should send message with severity info to Windows Event Log." do
+    expect(winevt).to receive(:info).with("*** Chef 12.4.0.dev.0 ***")
+    Chef::Log.info("*** Chef 12.4.0.dev.0 ***")
+  end
+
+  it "should send message with severity warning to Windows Event Log." do
+    expect(winevt).to receive(:warn).with("No config file found or specified on command line, using command line options.")
+    Chef::Log.warn("No config file found or specified on command line, using command line options.")
+  end
+
+  it "should fallback into send message with severity info to Windows Event Log when wrong format." do
+    expect(winevt).to receive(:info).with("chef message")
+    winevt.write("chef message")
+  end
+end
diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb
index ec39174..7f6d315 100644
--- a/spec/unit/lwrp_spec.rb
+++ b/spec/unit/lwrp_spec.rb
@@ -17,20 +17,40 @@
 #
 
 require 'spec_helper'
+require 'tmpdir'
+require 'fileutils'
+require 'chef/mixin/convert_to_class_name'
 
 module LwrpConstScopingConflict
 end
 
 describe "LWRP" do
+  include Chef::Mixin::ConvertToClassName
+
   before do
     @original_VERBOSE = $VERBOSE
     $VERBOSE = nil
+    Chef::Resource::LWRPBase.class_eval { @loaded_lwrps = {} }
   end
 
   after do
     $VERBOSE = @original_VERBOSE
   end
 
+  def get_lwrp(name)
+    Chef::ResourceResolver.resolve(name)
+  end
+
+  def get_lwrp_provider(name)
+    old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+    Chef::Config[:treat_deprecation_warnings_as_errors] = false
+    begin
+      Chef::Provider.const_get(convert_to_class_name(name.to_s))
+    ensure
+      Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+    end
+  end
+
   describe "when overriding an existing class" do
     before :each do
       allow($stderr).to receive(:write)
@@ -43,7 +63,6 @@ describe "LWRP" do
       expect(Chef::Log).not_to receive(:debug).with(/anymore/)
       Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
       Object.send(:remove_const, 'LwrpFoo')
-      Chef::Resource.send(:remove_const, 'LwrpFoo')
     end
 
     it "should not skip loading a provider when there's a top level symbol of the same name" do
@@ -53,7 +72,6 @@ describe "LWRP" do
       expect(Chef::Log).not_to receive(:debug).with(/anymore/)
       Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil)
       Object.send(:remove_const, 'LwrpBuckPasser')
-      Chef::Provider.send(:remove_const, 'LwrpBuckPasser')
     end
 
     # @todo: we need a before block to manually remove_const all of the LWRPs that we
@@ -67,7 +85,6 @@ describe "LWRP" do
 
       Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file|
         expect(Chef::Log).to receive(:info).with(/Skipping/)
-        expect(Chef::Log).to receive(:debug).with(/anymore/)
         Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
       end
     end
@@ -79,7 +96,6 @@ describe "LWRP" do
 
       Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file|
         expect(Chef::Log).to receive(:info).with(/Skipping/)
-        expect(Chef::Log).to receive(:debug).with(/anymore/)
         Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil)
       end
     end
@@ -90,7 +106,7 @@ describe "LWRP" do
       Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file|
         Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
       end
-      first_lwr_foo_class = Chef::Resource::LwrpFoo
+      first_lwr_foo_class = get_lwrp(:lwrp_foo)
       expect(Chef::Resource.resource_classes).to include(first_lwr_foo_class)
       Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file|
         Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
@@ -106,40 +122,91 @@ describe "LWRP" do
 
   end
 
-  describe "Lightweight Chef::Resource" do
+  context "When an LWRP resource in cookbook l-w-r-p is loaded" do
+    before do
+      @tmpdir = Dir.mktmpdir("lwrp_test")
+      resource_path = File.join(@tmpdir, "foo.rb")
+      IO.write(resource_path, "default_action :create")
+      provider_path = File.join(@tmpdir, "foo.rb")
+      IO.write(provider_path, <<-EOM)
+        action :create do
+          raise "hi"
+        end
+      EOM
+    end
+
+    it "Can find the resource at l_w_r_p_foo" do
+    end
+  end
 
+  context "When an LWRP resource lwrp_foo is loaded" do
     before do
-      Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file|
-        Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
+      @tmpdir = Dir.mktmpdir("lwrp_test")
+      @lwrp_path = File.join(@tmpdir, "foo.rb")
+      content = IO.read(File.expand_path("../../data/lwrp/resources/foo.rb", __FILE__))
+      IO.write(@lwrp_path, content)
+      Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil)
+      @original_resource = Chef::ResourceResolver.resolve(:lwrp_foo)
+    end
+
+    after do
+      FileUtils.remove_entry @tmpdir
+    end
+
+    context "And the LWRP is asked to load again, this time with different code" do
+      before do
+        content = IO.read(File.expand_path("../../data/lwrp_override/resources/foo.rb", __FILE__))
+        IO.write(@lwrp_path, content)
+        Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil)
+      end
+
+      it "Should load the old content, and not the new" do
+        resource = Chef::ResourceResolver.resolve(:lwrp_foo)
+        expect(resource).to eq @original_resource
+        expect(resource.default_action).to eq([:pass_buck])
+        expect(Chef.method_defined?(:method_created_by_override_lwrp_foo)).to be_falsey
       end
+    end
+  end
+
+  describe "Lightweight Chef::Resource" do
 
-      Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "resources", "*"))].each do |file|
+    before do
+      Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file|
         Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
       end
     end
 
-    it "should load the resource into a properly-named class" do
-      expect(Chef::Resource.const_get("LwrpFoo")).to be_kind_of(Class)
+    it "should be resolvable with Chef::ResourceResolver.resolve(:lwrp_foo)" do
+      expect(Chef::ResourceResolver.resolve(:lwrp_foo, node: Chef::Node.new)).to eq(get_lwrp(:lwrp_foo))
     end
 
     it "should set resource_name" do
-      expect(Chef::Resource::LwrpFoo.new("blah").resource_name).to eql(:lwrp_foo)
+      expect(get_lwrp(:lwrp_foo).new("blah").resource_name).to eql(:lwrp_foo)
+    end
+
+    it "should output the resource_name in .to_s" do
+      expect(get_lwrp(:lwrp_foo).new("blah").to_s).to eq "lwrp_foo[blah]"
+    end
+
+    it "should have a class that outputs a reasonable string" do
+      expect(get_lwrp(:lwrp_foo).to_s).to eq "Custom resource lwrp_foo from cookbook lwrp"
     end
 
     it "should add the specified actions to the allowed_actions array" do
-      expect(Chef::Resource::LwrpFoo.new("blah").allowed_actions).to include(:pass_buck, :twiddle_thumbs)
+      expect(get_lwrp(:lwrp_foo).new("blah").allowed_actions).to include(:pass_buck, :twiddle_thumbs)
     end
 
     it "should set the specified action as the default action" do
-      expect(Chef::Resource::LwrpFoo.new("blah").action).to eq(:pass_buck)
+      expect(get_lwrp(:lwrp_foo).new("blah").action).to eq([:pass_buck])
     end
 
     it "should create a method for each attribute" do
-      expect(Chef::Resource::LwrpFoo.new("blah").methods.map{ |m| m.to_sym}).to include(:monkey)
+      expect(get_lwrp(:lwrp_foo).new("blah").methods.map{ |m| m.to_sym}).to include(:monkey)
     end
 
     it "should build attribute methods that respect validation rules" do
-      expect { Chef::Resource::LwrpFoo.new("blah").monkey(42) }.to raise_error(ArgumentError)
+      expect { get_lwrp(:lwrp_foo).new("blah").monkey(42) }.to raise_error(ArgumentError)
     end
 
     it "should have access to the run context and node during class definition" do
@@ -151,7 +218,7 @@ describe "LWRP" do
         Chef::Resource::LWRPBase.build_from_file("lwrp", file, run_context)
       end
 
-      cls = Chef::Resource.const_get("LwrpNodeattr")
+      cls = get_lwrp(:lwrp_nodeattr)
       expect(cls.node).to be_kind_of(Chef::Node)
       expect(cls.run_context).to be_kind_of(Chef::RunContext)
       expect(cls.node[:penguin_name]).to eql("jackass")
@@ -175,14 +242,6 @@ describe "LWRP" do
         expect(klass.resource_name).to eq(:foo)
       end
 
-      context "when creating a new instance" do
-        it "raises an exception if resource_name is nil" do
-          expect {
-            klass.new('blah')
-          }.to raise_error(Chef::Exceptions::InvalidResourceSpecification)
-        end
-      end
-
       context "lazy default values" do
         let(:klass) do
           Class.new(Chef::Resource::LWRPBase) do
@@ -225,17 +284,17 @@ describe "LWRP" do
         end
       end
 
-      context "when the child does not defined the methods" do
+      context "when the child does not define the methods" do
         let(:child) do
           Class.new(parent)
         end
 
         it "delegates #actions to the parent" do
-          expect(child.actions).to eq([:eat, :sleep])
+          expect(child.actions).to eq([:nothing, :eat, :sleep])
         end
 
         it "delegates #default_action to the parent" do
-          expect(child.default_action).to eq(:eat)
+          expect(child.default_action).to eq([:eat])
         end
       end
 
@@ -248,11 +307,11 @@ describe "LWRP" do
         end
 
         it "does not delegate #actions to the parent" do
-          expect(child.actions).to eq([:dont_eat, :dont_sleep])
+          expect(child.actions).to eq([:nothing, :dont_eat, :dont_sleep])
         end
 
         it "does not delegate #default_action to the parent" do
-          expect(child.default_action).to eq(:dont_eat)
+          expect(child.default_action).to eq([:dont_eat])
         end
       end
 
@@ -273,110 +332,193 @@ describe "LWRP" do
 
         it "amends actions when they are already defined" do
           raise_if_deprecated!
-          expect(child.actions).to eq([:eat, :sleep, :drink])
+          expect(child.actions).to eq([:nothing, :eat, :sleep, :drink])
+        end
+      end
+    end
+
+    describe "when actions is set to an array" do
+      let(:resource_class) do
+        Class.new(Chef::Resource::LWRPBase) do
+          actions [ :eat, :sleep ]
         end
       end
+      let(:resource) do
+        resource_class.new('blah')
+      end
+      it "actions includes those actions" do
+        expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ]
+      end
+      it "allowed_actions includes those actions" do
+        expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+      end
+      it "resource.allowed_actions includes those actions" do
+        expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+      end
     end
 
+    describe "when allowed_actions is set to an array" do
+      let(:resource_class) do
+        Class.new(Chef::Resource::LWRPBase) do
+          allowed_actions [ :eat, :sleep ]
+        end
+      end
+      let(:resource) do
+        resource_class.new('blah')
+      end
+      it "actions includes those actions" do
+        expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ]
+      end
+      it "allowed_actions includes those actions" do
+        expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+      end
+      it "resource.allowed_actions includes those actions" do
+        expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+      end
+    end
   end
 
   describe "Lightweight Chef::Provider" do
-    before do
-      @node = Chef::Node.new
-      @node.automatic[:platform] = :ubuntu
-      @node.automatic[:platform_version] = '8.10'
-      @events = Chef::EventDispatch::Dispatcher.new
-      @run_context = Chef::RunContext.new(@node, Chef::CookbookCollection.new({}), @events)
-      @runner = Chef::Runner.new(@run_context)
-    end
 
-    before(:each) do
-      Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file|
-        Chef::Resource::LWRPBase.build_from_file("lwrp", file, @run_context)
+    let(:node) do
+      Chef::Node.new.tap do |n|
+        n.automatic[:platform] = :ubuntu
+        n.automatic[:platform_version] = '8.10'
       end
+    end
 
-      Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "resources", "*"))].each do |file|
-        Chef::Resource::LWRPBase.build_from_file("lwrp", file, @run_context)
-      end
+    let(:events) { Chef::EventDispatch::Dispatcher.new }
 
-      Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "providers", "*"))].each do |file|
-        Chef::Provider::LWRPBase.build_from_file("lwrp", file, @run_context)
-      end
+    let(:run_context) { Chef::RunContext.new(node, Chef::CookbookCollection.new({}), events) }
+
+    let(:runner) { Chef::Runner.new(run_context) }
+
+    let(:lwrp_cookbok_name) { "lwrp" }
+
+    before do
+      Chef::Provider::LWRPBase.class_eval { @loaded_lwrps = {} }
+    end
 
-      Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "providers", "*"))].each do |file|
-        Chef::Provider::LWRPBase.build_from_file("lwrp", file, @run_context)
+    before(:each) do
+      Dir[File.expand_path(File.expand_path("../../data/lwrp/resources/*", __FILE__))].each do |file|
+        Chef::Resource::LWRPBase.build_from_file(lwrp_cookbok_name, file, run_context)
       end
 
+      Dir[File.expand_path(File.expand_path("../../data/lwrp/providers/*", __FILE__))].each do |file|
+        Chef::Provider::LWRPBase.build_from_file(lwrp_cookbok_name, file, run_context)
+      end
     end
 
     it "should properly handle a new_resource reference" do
-      resource = Chef::Resource::LwrpFoo.new("morpheus")
+      resource = get_lwrp(:lwrp_foo).new("morpheus", run_context)
       resource.monkey("bob")
-      resource.provider(:lwrp_monkey_name_printer)
-      resource.run_context = @run_context
+      resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer))
 
       provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs)
       provider.action_twiddle_thumbs
     end
 
-    it "should load the provider into a properly-named class" do
-      expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class)
-    end
+    context "provider class created" do
+      before do
+        @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+        Chef::Config[:treat_deprecation_warnings_as_errors] = false
+      end
 
-    it "should create a method for each attribute" do
-      new_resource = double("new resource").as_null_object
-      expect(Chef::Provider::LwrpBuckPasser.new(nil, new_resource).methods.map{|m|m.to_sym}).to include(:action_pass_buck)
-      expect(Chef::Provider::LwrpThumbTwiddler.new(nil, new_resource).methods.map{|m|m.to_sym}).to include(:action_twiddle_thumbs)
+      after do
+        Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors
+      end
+
+      it "should load the provider into a properly-named class" do
+        expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class)
+        expect(Chef::Provider::LwrpBuckPasser <= Chef::Provider::LWRPBase).to be_truthy
+      end
+
+      it "should create a method for each action" do
+        expect(get_lwrp_provider(:lwrp_buck_passer).instance_methods).to include(:action_pass_buck)
+        expect(get_lwrp_provider(:lwrp_thumb_twiddler).instance_methods).to include(:action_twiddle_thumbs)
+      end
+
+      it "sets itself as a provider for a resource of the same name" do
+        found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :lwrp_buck_passer)
+        # we bypass the per-file loading to get the file to load each time,
+        # which creates the LWRP class repeatedly. New things get prepended to
+        # the list of providers.
+        expect(found_providers.first).to eq(get_lwrp_provider(:lwrp_buck_passer))
+      end
+
+      context "with a cookbook with an underscore in the name" do
+
+        let(:lwrp_cookbok_name) { "l_w_r_p" }
+
+        it "sets itself as a provider for a resource of the same name" do
+          found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer)
+          expect(found_providers.size).to eq(1)
+          expect(found_providers.last).to eq(get_lwrp_provider(:l_w_r_p_buck_passer))
+        end
+      end
+
+      context "with a cookbook with a hypen in the name" do
+
+        let(:lwrp_cookbok_name) { "l-w-r-p" }
+
+        it "sets itself as a provider for a resource of the same name" do
+          incorrect_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :'l-w-r-p_buck_passer')
+          expect(incorrect_providers).to eq([])
+
+          found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer)
+          expect(found_providers.first).to eq(get_lwrp_provider(:l_w_r_p_buck_passer))
+        end
+      end
     end
 
     it "should insert resources embedded in the provider into the middle of the resource collection" do
-      injector = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+      injector = get_lwrp(:lwrp_foo).new("morpheus", run_context)
       injector.action(:pass_buck)
-      injector.provider(:lwrp_buck_passer)
-      dummy = Chef::Resource::ZenMaster.new("keanu reeves", @run_context)
+      injector.provider(get_lwrp_provider(:lwrp_buck_passer))
+      dummy = Chef::Resource::ZenMaster.new("keanu reeves", run_context)
       dummy.provider(Chef::Provider::Easy)
-      @run_context.resource_collection.insert(injector)
-      @run_context.resource_collection.insert(dummy)
+      run_context.resource_collection.insert(injector)
+      run_context.resource_collection.insert(dummy)
 
-      Chef::Runner.new(@run_context).converge
+      Chef::Runner.new(run_context).converge
 
-      expect(@run_context.resource_collection[0]).to eql(injector)
-      expect(@run_context.resource_collection[1].name).to eql('prepared_thumbs')
-      expect(@run_context.resource_collection[2].name).to eql('twiddled_thumbs')
-      expect(@run_context.resource_collection[3]).to eql(dummy)
+      expect(run_context.resource_collection[0]).to eql(injector)
+      expect(run_context.resource_collection[1].name).to eql('prepared_thumbs')
+      expect(run_context.resource_collection[2].name).to eql('twiddled_thumbs')
+      expect(run_context.resource_collection[3]).to eql(dummy)
     end
 
     it "should insert embedded resources from multiple providers, including from the last position, properly into the resource collection" do
-      injector = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+      injector = get_lwrp(:lwrp_foo).new("morpheus", run_context)
       injector.action(:pass_buck)
-      injector.provider(:lwrp_buck_passer)
+      injector.provider(get_lwrp_provider(:lwrp_buck_passer))
 
-      injector2 = Chef::Resource::LwrpBar.new("tank", @run_context)
+      injector2 = get_lwrp(:lwrp_bar).new("tank", run_context)
       injector2.action(:pass_buck)
-      injector2.provider(:lwrp_buck_passer_2)
+      injector2.provider(get_lwrp_provider(:lwrp_buck_passer_2))
 
-      dummy = Chef::Resource::ZenMaster.new("keanu reeves", @run_context)
+      dummy = Chef::Resource::ZenMaster.new("keanu reeves", run_context)
       dummy.provider(Chef::Provider::Easy)
 
-      @run_context.resource_collection.insert(injector)
-      @run_context.resource_collection.insert(dummy)
-      @run_context.resource_collection.insert(injector2)
+      run_context.resource_collection.insert(injector)
+      run_context.resource_collection.insert(dummy)
+      run_context.resource_collection.insert(injector2)
 
-      Chef::Runner.new(@run_context).converge
+      Chef::Runner.new(run_context).converge
 
-      expect(@run_context.resource_collection[0]).to eql(injector)
-      expect(@run_context.resource_collection[1].name).to eql('prepared_thumbs')
-      expect(@run_context.resource_collection[2].name).to eql('twiddled_thumbs')
-      expect(@run_context.resource_collection[3]).to eql(dummy)
-      expect(@run_context.resource_collection[4]).to eql(injector2)
-      expect(@run_context.resource_collection[5].name).to eql('prepared_eyes')
-      expect(@run_context.resource_collection[6].name).to eql('dried_paint_watched')
+      expect(run_context.resource_collection[0]).to eql(injector)
+      expect(run_context.resource_collection[1].name).to eql('prepared_thumbs')
+      expect(run_context.resource_collection[2].name).to eql('twiddled_thumbs')
+      expect(run_context.resource_collection[3]).to eql(dummy)
+      expect(run_context.resource_collection[4]).to eql(injector2)
+      expect(run_context.resource_collection[5].name).to eql('prepared_eyes')
+      expect(run_context.resource_collection[6].name).to eql('dried_paint_watched')
     end
 
     it "should properly handle a new_resource reference" do
-      resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+      resource = get_lwrp(:lwrp_foo).new("morpheus", run_context)
       resource.monkey("bob")
-      resource.provider(:lwrp_monkey_name_printer)
+      resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer))
 
       provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs)
       provider.action_twiddle_thumbs
@@ -385,9 +527,9 @@ describe "LWRP" do
     end
 
     it "should properly handle an embedded Resource accessing the enclosing Provider's scope" do
-      resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+      resource = get_lwrp(:lwrp_foo).new("morpheus", run_context)
       resource.monkey("bob")
-      resource.provider(:lwrp_embedded_resource_accesses_providers_scope)
+      resource.provider(get_lwrp_provider(:lwrp_embedded_resource_accesses_providers_scope))
 
       provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs)
       #provider = @runner.build_provider(resource)
@@ -404,15 +546,15 @@ describe "LWRP" do
         # Side effect of lwrp_inline_compiler provider for testing notifications.
         $interior_ruby_block_2 = nil
         # resource type doesn't matter, so make an existing resource type work with provider.
-        @resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+        @resource = get_lwrp(:lwrp_foo).new("morpheus", run_context)
         @resource.allowed_actions << :test
         @resource.action(:test)
-        @resource.provider(:lwrp_inline_compiler)
+        @resource.provider(get_lwrp_provider(:lwrp_inline_compiler))
       end
 
       it "does not add interior resources to the exterior resource collection" do
         @resource.run_action(:test)
-        expect(@run_context.resource_collection).to be_empty
+        expect(run_context.resource_collection).to be_empty
       end
 
       context "when interior resources are updated" do
@@ -437,7 +579,144 @@ describe "LWRP" do
       end
 
     end
-
   end
 
+  context "resource class created" do
+    before(:context) do
+      @tmpdir = Dir.mktmpdir("lwrp_test")
+      resource_path = File.join(@tmpdir, "once.rb")
+      IO.write(resource_path, "default_action :create")
+
+      @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+      Chef::Config[:treat_deprecation_warnings_as_errors] = false
+      Chef::Resource::LWRPBase.build_from_file("lwrp", resource_path, nil)
+    end
+
+    after(:context) do
+      FileUtils.remove_entry @tmpdir
+      Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors
+    end
+
+    it "should load the resource into a properly-named class" do
+      expect(Chef::Resource::LwrpOnce).to be_kind_of(Class)
+      expect(Chef::Resource::LwrpOnce <= Chef::Resource::LWRPBase).to be_truthy
+    end
+
+    it "get_lwrp(:lwrp_once).new is a Chef::Resource::LwrpOnce" do
+      lwrp = get_lwrp(:lwrp_once).new('hi')
+      expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy
+      expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy
+      expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+      expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+    end
+
+    it "Chef::Resource::LwrpOnce.new is a get_lwrp(:lwrp_once)" do
+      lwrp = Chef::Resource::LwrpOnce.new('hi')
+      expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy
+      expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy
+      expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+      expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+    end
+
+    it "works even if LwrpOnce exists in the top level" do
+      module ::LwrpOnce
+      end
+      expect(Chef::Resource::LwrpOnce).not_to eq(::LwrpOnce)
+    end
+
+    it "allows monkey patching of the lwrp through Chef::Resource" do
+      monkey = Module.new do
+        def issue_3607
+        end
+      end
+      Chef::Resource::LwrpOnce.send(:include, monkey)
+      expect { get_lwrp(:lwrp_once).new("blah").issue_3607 }.not_to raise_error
+    end
+
+    context "with a subclass of get_lwrp(:lwrp_once)" do
+      let(:subclass) do
+        Class.new(get_lwrp(:lwrp_once))
+      end
+
+      it "subclass.new is a subclass" do
+        lwrp = subclass.new('hi')
+        expect(lwrp.kind_of?(subclass)).to be_truthy
+        expect(lwrp.is_a?(subclass)).to be_truthy
+        expect(subclass === lwrp).to be_truthy
+        expect(lwrp.class === subclass)
+      end
+      it "subclass.new is a Chef::Resource::LwrpOnce" do
+        lwrp = subclass.new('hi')
+        expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy
+        expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy
+        expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+        expect(lwrp.class === Chef::Resource::LwrpOnce)
+      end
+      it "subclass.new is a get_lwrp(:lwrp_once)" do
+        lwrp = subclass.new('hi')
+        expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy
+        expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy
+        expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+        expect(lwrp.class === get_lwrp(:lwrp_once))
+      end
+      it "Chef::Resource::LwrpOnce.new is *not* a subclass" do
+        lwrp = Chef::Resource::LwrpOnce.new('hi')
+        expect(lwrp.kind_of?(subclass)).to be_falsey
+        expect(lwrp.is_a?(subclass)).to be_falsey
+        expect(subclass === lwrp.class).to be_falsey
+        expect(subclass === Chef::Resource::LwrpOnce).to be_falsey
+      end
+      it "get_lwrp(:lwrp_once).new is *not* a subclass" do
+        lwrp = get_lwrp(:lwrp_once).new('hi')
+        expect(lwrp.kind_of?(subclass)).to be_falsey
+        expect(lwrp.is_a?(subclass)).to be_falsey
+        expect(subclass === lwrp.class).to be_falsey
+        expect(subclass === get_lwrp(:lwrp_once)).to be_falsey
+      end
+    end
+
+    context "with a subclass of Chef::Resource::LwrpOnce" do
+      let(:subclass) do
+        Class.new(Chef::Resource::LwrpOnce)
+      end
+
+      it "subclass.new is a subclass" do
+        lwrp = subclass.new('hi')
+        expect(lwrp.kind_of?(subclass)).to be_truthy
+        expect(lwrp.is_a?(subclass)).to be_truthy
+        expect(subclass === lwrp).to be_truthy
+        expect(lwrp.class === subclass)
+      end
+      it "subclass.new is a Chef::Resource::LwrpOnce" do
+        lwrp = subclass.new('hi')
+        expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy
+        expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy
+        expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+        expect(lwrp.class === Chef::Resource::LwrpOnce)
+      end
+      it "subclass.new is a get_lwrp(:lwrp_once)" do
+        lwrp = subclass.new('hi')
+        expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy
+        expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy
+        expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+        expect(lwrp.class === get_lwrp(:lwrp_once))
+      end
+      it "Chef::Resource::LwrpOnce.new is *not* a subclass" do
+        lwrp = Chef::Resource::LwrpOnce.new('hi')
+        expect(lwrp.kind_of?(subclass)).to be_falsey
+        expect(lwrp.is_a?(subclass)).to be_falsey
+        expect(subclass === lwrp.class).to be_falsey
+        expect(subclass === Chef::Resource::LwrpOnce).to be_falsey
+      end
+      it "get_lwrp(:lwrp_once).new is *not* a subclass" do
+        lwrp = get_lwrp(:lwrp_once).new('hi')
+        expect(lwrp.kind_of?(subclass)).to be_falsey
+        expect(lwrp.is_a?(subclass)).to be_falsey
+        expect(subclass === lwrp.class).to be_falsey
+        expect(subclass === get_lwrp(:lwrp_once)).to be_falsey
+      end
+    end
+  end
 end
+
+
diff --git a/spec/unit/mixin/api_version_request_handling_spec.rb b/spec/unit/mixin/api_version_request_handling_spec.rb
new file mode 100644
index 0000000..cc5340e
--- /dev/null
+++ b/spec/unit/mixin/api_version_request_handling_spec.rb
@@ -0,0 +1,127 @@
+#
+# Author:: Tyler Cloke (tyler at chef.io)
+# Copyright:: Copyright 2015 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 'spec_helper'
+
+describe Chef::Mixin::ApiVersionRequestHandling do
+  let(:dummy_class) { Class.new { include Chef::Mixin::ApiVersionRequestHandling } }
+  let(:object) { dummy_class.new }
+
+  describe ".server_client_api_version_intersection" do
+    let(:default_supported_client_versions) { [0,1,2] }
+
+
+    context "when the response code is not 406" do
+      let(:response) { OpenStruct.new(:code => '405') }
+      let(:exception) { Net::HTTPServerException.new("405 Something Else", response) }
+
+      it "returns nil" do
+        expect(object.server_client_api_version_intersection(exception, default_supported_client_versions)).
+          to be_nil
+      end
+
+    end # when the response code is not 406
+
+    context "when the response code is 406" do
+      let(:response) { OpenStruct.new(:code => '406') }
+      let(:exception) { Net::HTTPServerException.new("406 Not Acceptable", response) }
+
+      context "when x-ops-server-api-version header does not exist" do
+        it "returns nil" do
+          expect(object.server_client_api_version_intersection(exception, default_supported_client_versions)).
+            to be_nil
+        end
+      end # when x-ops-server-api-version header does not exist
+
+      context "when x-ops-server-api-version header exists" do
+        let(:min_server_version) { 2 }
+        let(:max_server_version) { 4 }
+        let(:return_hash) {
+          {
+            "min_version" => min_server_version,
+            "max_version" => max_server_version
+          }
+        }
+
+        before(:each) do
+          allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash))
+        end
+
+        context "when there is no intersection between client and server versions" do
+          shared_examples_for "no intersection between client and server versions" do
+            it "return an array" do
+              expect(object.server_client_api_version_intersection(exception, supported_client_versions)).
+                to be_a_kind_of(Array)
+            end
+
+            it "returns an empty array" do
+              expect(object.server_client_api_version_intersection(exception, supported_client_versions).length).
+                to eq(0)
+            end
+
+          end
+
+          context "when all the versions are higher than the max" do
+            it_should_behave_like "no intersection between client and server versions" do
+              let(:supported_client_versions) { [5,6,7] }
+            end
+          end
+
+          context "when all the versions are lower than the min" do
+            it_should_behave_like "no intersection between client and server versions" do
+              let(:supported_client_versions) { [0,1] }
+            end
+          end
+
+        end # when there is no intersection between client and server versions
+
+        context "when there is an intersection between client and server versions" do
+          context "when multiple versions intersect" do
+            let(:supported_client_versions) { [1,2,3,4,5] }
+
+            it "includes all of the intersection" do
+              expect(object.server_client_api_version_intersection(exception, supported_client_versions)).
+                to eq([2,3,4])
+            end
+          end # when multiple versions intersect
+
+          context "when only the min client version intersects" do
+            let(:supported_client_versions) { [0,1,2] }
+
+            it "includes the intersection" do
+              expect(object.server_client_api_version_intersection(exception, supported_client_versions)).
+                to eq([2])
+            end
+          end # when only the min client version intersects
+
+          context "when only the max client version intersects" do
+            let(:supported_client_versions) { [4,5,6] }
+
+            it "includes the intersection" do
+              expect(object.server_client_api_version_intersection(exception, supported_client_versions)).
+                to eq([4])
+            end
+          end # when only the max client version intersects
+
+        end # when there is an intersection between client and server versions
+
+      end # when x-ops-server-api-version header exists
+    end # when the response code is 406
+
+  end # .server_client_api_version_intersection
+end # Chef::Mixin::ApiVersionRequestHandling
diff --git a/spec/unit/mixin/command_spec.rb b/spec/unit/mixin/command_spec.rb
index e198e3a..050b261 100644
--- a/spec/unit/mixin/command_spec.rb
+++ b/spec/unit/mixin/command_spec.rb
@@ -22,7 +22,7 @@ describe Chef::Mixin::Command, :volatile do
 
   if windows?
 
-    pending("TODO MOVE: this is a platform specific integration test.")
+    skip("TODO MOVE: this is a platform specific integration test.")
 
   else
 
@@ -61,7 +61,6 @@ describe Chef::Mixin::Command, :volatile do
 
         it "returns immediately after the first child process exits" do
           expect {Timeout.timeout(10) do
-            pid, stdin,stdout,stderr = nil,nil,nil,nil
             evil_forker="exit if fork; 10.times { sleep 1}"
             popen4("ruby -e '#{evil_forker}'") do |pid,stdin,stdout,stderr|
             end
diff --git a/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb
index aeef175..4089262 100644
--- a/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb
+++ b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb
@@ -49,12 +49,12 @@ describe Chef::Mixin::EnforceOwnershipAndPermissions do
       allow_any_instance_of(Chef::FileAccessControl).to receive(:uid_from_resource).and_return(0)
       allow_any_instance_of(Chef::FileAccessControl).to receive(:requires_changes?).and_return(false)
       allow_any_instance_of(Chef::FileAccessControl).to receive(:define_resource_requirements)
+      allow_any_instance_of(Chef::FileAccessControl).to receive(:describe_changes)
+
+      passwd_struct = OpenStruct.new(:name => "root", :passwd => "x",
+                                     :uid => 0, :gid => 0, :dir => '/root',
+                                     :shell => '/bin/bash')
 
-      passwd_struct = if windows?
-                        Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash")
-                      else
-                        Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash")
-                      end
       group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0)
       allow(Etc).to receive(:getpwuid).and_return(passwd_struct)
       allow(Etc).to receive(:getgrgid).and_return(group_struct)
@@ -73,12 +73,12 @@ describe Chef::Mixin::EnforceOwnershipAndPermissions do
     before do
       allow_any_instance_of(Chef::FileAccessControl).to receive(:requires_changes?).and_return(true)
       allow_any_instance_of(Chef::FileAccessControl).to receive(:uid_from_resource).and_return(0)
+      allow_any_instance_of(Chef::FileAccessControl).to receive(:describe_changes)
+
+      passwd_struct = OpenStruct.new(:name => "root", :passwd => "x",
+                                     :uid => 0, :gid => 0, :dir => '/root',
+                                     :shell => '/bin/bash')
 
-      passwd_struct = if windows?
-                        Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash")
-                      else
-                        Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash")
-                      end
       group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0)
       allow(Etc).to receive(:getpwuid).and_return(passwd_struct)
       allow(Etc).to receive(:getgrgid).and_return(group_struct)
diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb
index 85e1c1a..3724bbf 100644
--- a/spec/unit/mixin/params_validate_spec.rb
+++ b/spec/unit/mixin/params_validate_spec.rb
@@ -21,6 +21,8 @@ require 'spec_helper'
 class TinyClass
   include Chef::Mixin::ParamsValidate
 
+  attr_reader :name
+
   def music(is_good=true)
     is_good
   end
@@ -331,11 +333,11 @@ describe Chef::Mixin::ParamsValidate do
   it "asserts that a value returns false from a predicate method" do
     expect do
       @vo.validate({:not_blank => "should pass"},
-                   {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}})
+                   {:not_blank => {:cannot_be => [ :nil, :empty ]}})
     end.not_to raise_error
     expect do
       @vo.validate({:not_blank => ""},
-                   {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}})
+                   {:not_blank => {:cannot_be => [ :nil, :empty ]}})
     end.to raise_error(Chef::Exceptions::ValidationFailed)
   end
 
diff --git a/spec/unit/mixin/path_sanity_spec.rb b/spec/unit/mixin/path_sanity_spec.rb
index ec8e182..3a924b9 100644
--- a/spec/unit/mixin/path_sanity_spec.rb
+++ b/spec/unit/mixin/path_sanity_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Mixin::PathSanity do
       @gem_bindir = '/some/gem/bin'
       allow(Gem).to receive(:bindir).and_return(@gem_bindir)
       allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return(@ruby_bindir)
-      allow(Chef::Platform).to receive(:windows?).and_return(false)
+      allow(ChefConfig).to receive(:windows?).and_return(false)
     end
 
     it "adds all useful PATHs even if environment is an empty hash" do
@@ -77,7 +77,7 @@ describe Chef::Mixin::PathSanity do
       gem_bindir = 'C:\gems\bin'
       allow(Gem).to receive(:bindir).and_return(gem_bindir)
       allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return(ruby_bindir)
-      allow(Chef::Platform).to receive(:windows?).and_return(true)
+      allow(ChefConfig).to receive(:windows?).and_return(true)
       env = {"PATH" => 'C:\Windows\system32;C:\mr\softie'}
       @sanity.enforce_path_sanity(env)
       expect(env["PATH"]).to eq("C:\\Windows\\system32;C:\\mr\\softie;#{ruby_bindir};#{gem_bindir}")
diff --git a/spec/unit/mixin/powershell_out_spec.rb b/spec/unit/mixin/powershell_out_spec.rb
new file mode 100644
index 0000000..0fede58
--- /dev/null
+++ b/spec/unit/mixin/powershell_out_spec.rb
@@ -0,0 +1,70 @@
+#
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/mixin/powershell_out'
+
+describe Chef::Mixin::PowershellOut do
+  let(:shell_out_class) { Class.new { include Chef::Mixin::PowershellOut } }
+  subject(:object) { shell_out_class.new }
+  let(:architecture) { "something"  }
+  let(:flags) {
+     "-NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -InputFormat None"
+  }
+
+  describe "#powershell_out" do
+    it "runs a command and returns the shell_out object" do
+      ret = double("Mixlib::ShellOut")
+      expect(object).to receive(:shell_out).with(
+        "powershell.exe #{flags} -Command \"Get-Process\"",
+        {}
+      ).and_return(ret)
+      expect(object.powershell_out("Get-Process")).to eql(ret)
+    end
+
+    it "passes options" do
+      ret = double("Mixlib::ShellOut")
+      expect(object).to receive(:shell_out).with(
+        "powershell.exe #{flags} -Command \"Get-Process\"",
+        timeout: 600
+      ).and_return(ret)
+      expect(object.powershell_out("Get-Process", timeout: 600)).to eql(ret)
+    end
+  end
+
+  describe "#powershell_out!" do
+    it "runs a command and returns the shell_out object" do
+      mixlib_shellout = double("Mixlib::ShellOut")
+      expect(object).to receive(:shell_out).with(
+        "powershell.exe #{flags} -Command \"Get-Process\"",
+        {}
+      ).and_return(mixlib_shellout)
+      expect(mixlib_shellout).to receive(:error!)
+      expect(object.powershell_out!("Get-Process")).to eql(mixlib_shellout)
+    end
+
+    it "passes options" do
+      mixlib_shellout = double("Mixlib::ShellOut")
+      expect(object).to receive(:shell_out).with(
+        "powershell.exe #{flags} -Command \"Get-Process\"",
+        timeout: 600
+      ).and_return(mixlib_shellout)
+      expect(mixlib_shellout).to receive(:error!)
+      expect(object.powershell_out!("Get-Process", timeout: 600)).to eql(mixlib_shellout)
+    end
+  end
+end
diff --git a/spec/unit/mixin/properties_spec.rb b/spec/unit/mixin/properties_spec.rb
new file mode 100644
index 0000000..1817861
--- /dev/null
+++ b/spec/unit/mixin/properties_spec.rb
@@ -0,0 +1,97 @@
+require 'support/shared/integration/integration_helper'
+require 'chef/mixin/properties'
+
+module ChefMixinPropertiesSpec
+  describe "Chef::Resource.property" do
+    include IntegrationSupport
+
+    context "with a base class A with properties a, ab, and ac" do
+      class A
+        include Chef::Mixin::Properties
+        property :a, 'a', default: 'a'
+        property :ab, ['a', 'b'], default: 'a'
+        property :ac, ['a', 'c'], default: 'a'
+      end
+
+      context "and a module B with properties b, ab and bc" do
+        module B
+          include Chef::Mixin::Properties
+          property :b, 'b', default: 'b'
+          property :ab, default: 'b'
+          property :bc, ['b', 'c'], default: 'c'
+        end
+
+        context "and a derived class C < A with properties c, ac and bc" do
+          class C < A
+            include B
+            property :c, 'c', default: 'c'
+            property :ac, default: 'c'
+            property :bc, default: 'c'
+          end
+
+          it "A.properties has a, ab, and ac with types 'a', ['a', 'b'], and ['b', 'c']" do
+            expect(A.properties.keys).to eq [ :a, :ab, :ac ]
+            expect(A.properties[:a].validation_options[:is]).to eq 'a'
+            expect(A.properties[:ab].validation_options[:is]).to eq [ 'a', 'b' ]
+            expect(A.properties[:ac].validation_options[:is]).to eq [ 'a', 'c' ]
+          end
+          it "B.properties has b, ab, and bc with types 'b', nil and ['b', 'c']" do
+            expect(B.properties.keys).to eq [ :b, :ab, :bc ]
+            expect(B.properties[:b].validation_options[:is]).to eq 'b'
+            expect(B.properties[:ab].validation_options[:is]).to be_nil
+            expect(B.properties[:bc].validation_options[:is]).to eq [ 'b', 'c' ]
+          end
+          it "C.properties has a, b, c, ac and bc with merged types" do
+            expect(C.properties.keys).to eq [ :a, :ab, :ac, :b, :bc, :c ]
+            expect(C.properties[:a].validation_options[:is]).to eq 'a'
+            expect(C.properties[:b].validation_options[:is]).to eq 'b'
+            expect(C.properties[:c].validation_options[:is]).to eq 'c'
+            expect(C.properties[:ac].validation_options[:is]).to eq [ 'a', 'c' ]
+            expect(C.properties[:bc].validation_options[:is]).to eq [ 'b', 'c' ]
+          end
+          it "C.properties has ab with a non-merged type (from B)" do
+            expect(C.properties[:ab].validation_options[:is]).to be_nil
+          end
+
+          context "and an instance of C" do
+            let(:c) { C.new }
+
+            it "all properties can be retrieved and merged properties default to ab->b, ac->c, bc->c" do
+              expect(c.a).to  eq('a')
+              expect(c.b).to  eq('b')
+              expect(c.c).to  eq('c')
+              expect(c.ab).to eq('b')
+              expect(c.ac).to eq('c')
+              expect(c.bc).to eq('c')
+            end
+          end
+        end
+      end
+    end
+  end
+
+  context "with an Inner module" do
+    module Inner
+      include Chef::Mixin::Properties
+      property :inner
+    end
+
+    context "and an Outer module including it" do
+      module Outer
+        include Inner
+        property :outer
+      end
+
+      context "and an Outerest class including that" do
+        class Outerest
+          include Outer
+          property :outerest
+        end
+
+        it "Outerest.properties.validation_options[:is] inner, outer, outerest" do
+          expect(Outerest.properties.keys).to eq [:inner, :outer, :outerest]
+        end
+      end
+    end
+  end
+end
diff --git a/spec/unit/mixin/proxified_socket_spec.rb b/spec/unit/mixin/proxified_socket_spec.rb
new file mode 100644
index 0000000..88f71ae
--- /dev/null
+++ b/spec/unit/mixin/proxified_socket_spec.rb
@@ -0,0 +1,94 @@
+#
+# Author:: Tyler Ball (<tball at chef.io>)
+# Copyright:: Copyright (c) 2014 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 "spec_helper"
+require "chef/mixin/proxified_socket"
+require "proxifier/proxy"
+
+class TestProxifiedSocket
+  include Chef::Mixin::ProxifiedSocket
+end
+
+describe Chef::Mixin::ProxifiedSocket do
+
+  before do
+    @original_env = ENV.to_hash
+  end
+
+  after do
+    ENV.clear
+    ENV.update(@original_env)
+  end
+
+  let(:host) { "host" }
+  let(:port) { 7979 }
+  let(:test_instance) { TestProxifiedSocket.new }
+  let(:socket_double) { instance_double(TCPSocket)}
+  let(:proxifier_double) { instance_double(Proxifier::Proxy) }
+  let(:http_uri) { "http://somehost:1" }
+  let(:https_uri) { "https://somehost:1" }
+  let(:no_proxy_spec) { nil }
+
+  shared_examples "proxified socket" do
+    it "wraps the Socket in a Proxifier::Proxy" do
+      expect(Proxifier).to receive(:Proxy).with(proxy_uri, no_proxy: no_proxy_spec).and_return(proxifier_double)
+      expect(proxifier_double).to receive(:open).with(host, port).and_return(socket_double)
+      expect(test_instance.proxified_socket(host, port)).to eq(socket_double)
+    end
+  end
+
+  context "when no proxy is set" do
+    it "returns a plain TCPSocket" do
+      expect(TCPSocket).to receive(:new).with(host, port).and_return(socket_double)
+      expect(test_instance.proxified_socket(host, port)).to eq(socket_double)
+    end
+  end
+
+  context "when https_proxy is set" do
+    before do
+      # I'm purposefully setting both of these because we prefer the https
+      # variable
+      ENV['https_proxy'] = https_uri
+      ENV['http_proxy'] = http_uri
+    end
+
+    let(:proxy_uri) { https_uri }
+    include_examples "proxified socket"
+
+    context "when no_proxy is set" do
+      # This is testing that no_proxy is also provided to Proxified
+      # when it is set
+      before do
+        ENV['no_proxy'] = no_proxy_spec
+      end
+
+      let(:no_proxy_spec) { "somehost1,somehost2" }
+      include_examples "proxified socket"
+    end
+  end
+
+  context "when http_proxy is set" do
+    before do
+      ENV['http_proxy'] = http_uri
+    end
+
+    let(:proxy_uri) { http_uri }
+    include_examples "proxified socket"
+  end
+
+end
diff --git a/spec/unit/mixin/subclass_directive_spec.rb b/spec/unit/mixin/subclass_directive_spec.rb
new file mode 100644
index 0000000..552f26c
--- /dev/null
+++ b/spec/unit/mixin/subclass_directive_spec.rb
@@ -0,0 +1,45 @@
+#
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+class SubclassDirectiveParent
+  extend Chef::Mixin::SubclassDirective
+
+  subclass_directive :behave_differently
+end
+
+class SubclassDirectiveChild < SubclassDirectiveParent
+  behave_differently
+end
+
+class ChildWithoutDirective < SubclassDirectiveParent
+end
+
+describe Chef::Mixin::Uris do
+  let (:child) { SubclassDirectiveChild.new }
+
+  let (:other_child) { ChildWithoutDirective.new }
+
+  it "the child instance has the directive set" do
+    expect(child.behave_differently?).to be true
+  end
+
+  it "a child that does not declare it does not have it set" do
+    expect(other_child.behave_differently?).to be false
+  end
+end
diff --git a/spec/unit/mixin/template_spec.rb b/spec/unit/mixin/template_spec.rb
index f02bd34..95d0eb6 100644
--- a/spec/unit/mixin/template_spec.rb
+++ b/spec/unit/mixin/template_spec.rb
@@ -39,7 +39,7 @@ describe Chef::Mixin::Template, "render_template" do
 
   describe "when running on windows" do
     before do
-      allow(Chef::Platform).to receive(:windows?).and_return(true)
+      allow(ChefConfig).to receive(:windows?).and_return(true)
     end
 
     it "should render the templates with windows line endings" do
@@ -54,7 +54,7 @@ describe Chef::Mixin::Template, "render_template" do
 
   describe "when running on unix" do
     before do
-      allow(Chef::Platform).to receive(:windows?).and_return(false)
+      allow(ChefConfig).to receive(:windows?).and_return(false)
     end
 
     it "should render the templates with unix line endings" do
@@ -150,6 +150,11 @@ describe Chef::Mixin::Template, "render_template" do
       output == "before {super secret is candy} after"
     end
 
+    it "should pass the template finder to the partials" do
+      output = @template_context.render_template_from_string("before {<%= render 'nested_openldap_partials.erb', :variables => {:hello => 'Hello World!' } %>} after")
+      output == "before {Hello World!} after"
+    end
+
     it "should pass variables to partials" do
       output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {:secret => 'whatever' } %>} after")
       expect(output).to eq("before {super secret is whatever} after")
@@ -266,4 +271,3 @@ describe Chef::Mixin::Template, "render_template" do
     end
   end
 end
-
diff --git a/spec/unit/mixin/unformatter_spec.rb b/spec/unit/mixin/unformatter_spec.rb
new file mode 100644
index 0000000..2eae0ac
--- /dev/null
+++ b/spec/unit/mixin/unformatter_spec.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Jay Mundrawala (<jdm at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software
+# 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 'spec_helper'
+require 'chef/mixin/unformatter'
+
+class Chef::UnformatterTest
+  include Chef::Mixin::Unformatter
+
+  def foo
+  end
+
+end
+
+describe Chef::Mixin::Unformatter do
+  let (:unformatter) { Chef::UnformatterTest.new }
+  let (:message) { "Test Message" }
+
+  describe "#write" do
+    context "with a timestamp" do
+      it "sends foo to itself when the message is of severity foo" do
+        expect(unformatter).to receive(:foo).with(message)
+        unformatter.write("[time] foo: #{message}")
+      end
+
+      it "sends foo to itself when the message is of severity FOO" do
+        expect(unformatter).to receive(:foo).with(message)
+        unformatter.write("[time] FOO: #{message}")
+      end
+    end
+
+    context "without a timestamp" do
+      it "sends foo to itself when the message is of severity foo" do
+        expect(unformatter).to receive(:foo).with(message)
+        unformatter.write("foo: #{message}")
+      end
+
+      it "sends foo to itself when the message is of severity FOO" do
+        expect(unformatter).to receive(:foo).with(message)
+        unformatter.write("FOO: #{message}")
+      end
+    end
+
+  end
+
+end
diff --git a/spec/unit/mixin/uris_spec.rb b/spec/unit/mixin/uris_spec.rb
new file mode 100644
index 0000000..d4985c4
--- /dev/null
+++ b/spec/unit/mixin/uris_spec.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Jay Mundrawala (<jdm at chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/mixin/uris'
+
+class Chef::UrisTest
+  include Chef::Mixin::Uris
+end
+
+describe Chef::Mixin::Uris do
+  let (:uris) { Chef::UrisTest.new }
+
+  describe "#uri_scheme?" do
+    it "matches 'scheme://foo.com'" do
+      expect(uris.uri_scheme?('scheme://foo.com')).to eq(true)
+    end
+
+    it "does not match 'c:/foo.com'" do
+      expect(uris.uri_scheme?('c:/foo.com')).to eq(false)
+    end
+
+    it "does not match '/usr/bin/foo.com'" do
+      expect(uris.uri_scheme?('/usr/bin/foo.com')).to eq(false)
+    end
+
+    it "does not match 'c:/foo.com://bar.com'" do
+      expect(uris.uri_scheme?('c:/foo.com://bar.com')).to eq(false)
+    end
+  end
+
+  describe "#as_uri" do
+    it "parses a file scheme uri with spaces" do
+      expect{ uris.as_uri("file:///c:/foo bar.txt") }.not_to raise_exception
+    end
+
+    it "returns a URI object" do
+      expect( uris.as_uri("file:///c:/foo bar.txt") ).to be_a(URI)
+    end
+  end
+
+end
diff --git a/spec/unit/mixin/windows_architecture_helper_spec.rb b/spec/unit/mixin/windows_architecture_helper_spec.rb
index 3803d69..55eca28 100644
--- a/spec/unit/mixin/windows_architecture_helper_spec.rb
+++ b/spec/unit/mixin/windows_architecture_helper_spec.rb
@@ -60,23 +60,28 @@ describe Chef::Mixin::WindowsArchitectureHelper do
     end
   end
 
-  it "returns true for each supported desired architecture for all nodes with each valid architecture passed to node_supports_windows_architecture" do
-    enumerate_architecture_node_combinations(true)
+  it "returns true only for supported desired architecture passed to node_supports_windows_architecture" do
+    with_node_architecture_combinations do | node, desired_arch |
+        expect(node_supports_windows_architecture?(node, desired_arch)).to be true if (node_windows_architecture(node) == :x86_64 || desired_arch == :i386 )
+        expect(node_supports_windows_architecture?(node, desired_arch)).to be false if (node_windows_architecture(node)  == :i386 && desired_arch == :x86_64 )
+    end
   end
 
-  it "returns false for each unsupported desired architecture for all nodes with each valid architecture passed to node_supports_windows_architecture?" do
-    enumerate_architecture_node_combinations(true)
+  it "returns true only when forced_32bit_override_required? has 64-bit node architecture and 32-bit desired architecture" do
+    with_node_architecture_combinations do | node, desired_arch |
+      expect(forced_32bit_override_required?(node, desired_arch)).to be true if ((node_windows_architecture(node) == :x86_64) && (desired_arch == :i386) && !is_i386_process_on_x86_64_windows?)
+      expect(forced_32bit_override_required?(node, desired_arch)).to be false if ! ((node_windows_architecture(node) == :x86_64) && (desired_arch == :i386))
+    end
   end
 
-  def enumerate_architecture_node_combinations(only_valid_combinations)
+  def with_node_architecture_combinations
     @valid_architectures.each do | node_architecture |
       new_node = Chef::Node.new
       new_node.default["kernel"] = Hash.new
       new_node.default["kernel"][:machine] = node_architecture.to_s
 
-      @valid_architectures.each do | supported_architecture |
-        expect(node_supports_windows_architecture?(new_node, supported_architecture)).to eq(true) if only_valid_combinations && (supported_architecture != :x86_64 && node_architecture != :i386 )
-        expect(node_supports_windows_architecture?(new_node, supported_architecture)).to eq(false) if ! only_valid_combinations && (supported_architecture == :x86_64 && node_architecture == :i386 )
+      @valid_architectures.each do | architecture |
+        yield new_node, architecture if block_given?
       end
     end
   end
diff --git a/spec/unit/node_map_spec.rb b/spec/unit/node_map_spec.rb
index fe73729..7b37ea5 100644
--- a/spec/unit/node_map_spec.rb
+++ b/spec/unit/node_map_spec.rb
@@ -131,9 +131,25 @@ describe Chef::NodeMap do
       allow(node).to receive(:[]).with(:platform_version).and_return("6.0")
       expect(node_map.get(node, :thing)).to eql(nil)
     end
+
+    context "when there is a less specific definition" do
+      before do
+        node_map.set(:thing, :bar, platform_family: "rhel")
+      end
+
+      it "returns the value when the node matches" do
+        allow(node).to receive(:[]).with(:platform_family).and_return("rhel")
+        allow(node).to receive(:[]).with(:platform_version).and_return("7.0")
+        expect(node_map.get(node, :thing)).to eql(:foo)
+      end
+    end
   end
 
   describe "resource back-compat testing" do
+    before :each do
+      Chef::Config[:treat_deprecation_warnings_as_errors] = false
+    end
+
     it "should handle :on_platforms => :all" do
       node_map.set(:chef_gem, :foo, :on_platforms => :all)
       allow(node).to receive(:[]).with(:platform).and_return("windows")
@@ -152,4 +168,3 @@ describe Chef::NodeMap do
   end
 
 end
-
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index 5939403..4b57a93 100644
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -42,6 +42,14 @@ describe Chef::Node do
     expect([n3, n1, n2].sort).to eq([n1, n2, n3])
   end
 
+  it "should share identity only with others of the same name" do
+    n1 = Chef::Node.build('foo')
+    n2 = Chef::Node.build('foo')
+    n3 = Chef::Node.build('bar')
+    expect(n1).to eq(n2)
+    expect(n1).not_to eq(n3)
+  end
+
   describe "when the node does not exist on the server" do
     before do
       response = OpenStruct.new(:code => '404')
@@ -127,6 +135,78 @@ describe Chef::Node do
     end
   end
 
+  describe "policy_name" do
+
+    it "defaults to nil" do
+      expect(node.policy_name).to be_nil
+    end
+
+    it "sets policy_name with a regular setter" do
+      node.policy_name = "example-policy"
+      expect(node.policy_name).to eq("example-policy")
+    end
+
+    it "allows policy_name with every valid character" do
+      expect { node.policy_name = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqurstuvwxyz0123456789-_:.' }.to_not raise_error
+    end
+
+    it "sets policy_name when given an argument" do
+      node.policy_name("example-policy")
+      expect(node.policy_name).to eq("example-policy")
+    end
+
+    it "sets policy_name to nil when given nil" do
+      node.policy_name = "example-policy"
+      node.policy_name = nil
+      expect(node.policy_name).to be_nil
+    end
+
+    it "disallows non-strings" do
+      expect { node.policy_name(Hash.new) }.to raise_error(Chef::Exceptions::ValidationFailed)
+      expect { node.policy_name(42) }.to raise_error(Chef::Exceptions::ValidationFailed)
+    end
+
+    it "cannot be blank" do
+      expect { node.policy_name("")}.to raise_error(Chef::Exceptions::ValidationFailed)
+    end
+  end
+
+  describe "policy_group" do
+
+    it "defaults to nil" do
+      expect(node.policy_group).to be_nil
+    end
+
+    it "sets policy_group with a regular setter" do
+      node.policy_group = "staging"
+      expect(node.policy_group).to eq("staging")
+    end
+
+    it "allows policy_group with every valid character" do
+      expect { node.policy_group = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqurstuvwxyz0123456789-_:.' }.to_not raise_error
+    end
+
+    it "sets an environment with chef_environment(something)" do
+      node.policy_group("staging")
+      expect(node.policy_group).to eq("staging")
+    end
+
+    it "sets policy_group to nil when given nil" do
+      node.policy_group = "staging"
+      node.policy_group = nil
+      expect(node.policy_group).to be_nil
+    end
+
+    it "disallows non-strings" do
+      expect { node.policy_group(Hash.new) }.to raise_error(Chef::Exceptions::ValidationFailed)
+      expect { node.policy_group(42) }.to raise_error(Chef::Exceptions::ValidationFailed)
+    end
+
+    it "cannot be blank" do
+      expect { node.policy_group("")}.to raise_error(Chef::Exceptions::ValidationFailed)
+    end
+  end
+
   describe "attributes" do
     it "should have attributes" do
       expect(node.attribute).to be_a_kind_of(Hash)
@@ -672,6 +752,13 @@ describe Chef::Node do
       expect(node.run_list).to eq([ "role[base]", "recipe[chef::server]" ])
     end
 
+    it "sets the node chef_environment" do
+      attrs = { "chef_environment" => "foo_environment", "bar" => "baz" }
+      expect(node.consume_chef_environment(attrs)).to eq({ "bar" => "baz" })
+      expect(node.chef_environment).to eq("foo_environment")
+      expect(node['chef_environment']).to be nil
+    end
+
     it "should overwrites the run list with the run list it consumes" do
       node.consume_run_list "recipes" => [ "one", "two" ]
       node.consume_run_list "recipes" => [ "three" ]
@@ -706,7 +793,7 @@ describe Chef::Node do
     end
 
     it "should not set the tags attribute to an empty array if it is already defined" do
-      node.normal[:tags] = [ "radiohead" ]
+      node.tag("radiohead")
       node.consume_external_attrs(@ohai_data, {})
       expect(node.tags).to eql([ "radiohead" ])
     end
@@ -1106,7 +1193,44 @@ describe Chef::Node do
       expect(serialized_node.run_list).to eq(node.run_list)
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    context "when policyfile attributes are not present" do
+
+      it "does not have a policy_name key in the json" do
+        expect(node.for_json.keys).to_not include("policy_name")
+      end
+
+      it "does not have a policy_group key in the json" do
+        expect(node.for_json.keys).to_not include("policy_name")
+      end
+    end
+
+    context "when policyfile attributes are present" do
+
+      before do
+        node.policy_name = "my-application"
+        node.policy_group = "staging"
+      end
+
+      it "includes policy_name key in the json" do
+        expect(node.for_json).to have_key("policy_name")
+        expect(node.for_json["policy_name"]).to eq("my-application")
+      end
+
+      it "includes a policy_group key in the json" do
+        expect(node.for_json).to have_key("policy_group")
+        expect(node.for_json["policy_group"]).to eq("staging")
+      end
+
+      it "parses policyfile attributes from JSON" do
+        round_tripped_node = Chef::Node.json_create(node.for_json)
+
+        expect(round_tripped_node.policy_name).to eq("my-application")
+        expect(round_tripped_node.policy_group).to eq("staging")
+      end
+
+    end
+
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) {
         node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA))
         node
@@ -1301,6 +1425,110 @@ describe Chef::Node do
           node.save
         end
       end
+
+      context "when policyfile attributes are present" do
+
+        before do
+          node.name("example-node")
+          node.policy_name = "my-application"
+          node.policy_group = "staging"
+        end
+
+        context "and the server supports policyfile attributes in node JSON" do
+
+          it "creates the object normally" do
+            expect(@rest).to receive(:post_rest).with("nodes", node.for_json)
+            node.create
+          end
+
+          it "saves the node object normally" do
+            expect(@rest).to receive(:put_rest).with("nodes/example-node", node.for_json)
+            node.save
+          end
+        end
+
+        # Chef Server before 12.3
+        context "and the Chef Server does not support policyfile attributes in node JSON" do
+
+          let(:response_body) { %q[{"error":["Invalid key policy_name in request body"]}] }
+
+          let(:response) do
+            Net::HTTPResponse.send(:response_class, "400").new("1.0", "400", "Bad Request").tap do |r|
+              allow(r).to receive(:body).and_return(response_body)
+            end
+          end
+
+          let(:http_exception) do
+            begin
+              response.error!
+            rescue => e
+              e
+            end
+          end
+
+          let(:trimmed_node) do
+            node.for_json.tap do |j|
+              j.delete("policy_name")
+              j.delete("policy_group")
+            end
+
+          end
+
+          context "on Chef Client 13 and later" do
+
+            # Though we normally attempt to provide compatibility with chef
+            # server one major version back, policyfiles were beta when we
+            # added the policyfile attributes to the node JSON, therefore
+            # policyfile users need to be on 12.3 minimum when upgrading Chef
+            # Client to 13+
+            it "lets the 400 pass through", :chef_gte_13_only do
+              expect { node.save }.to raise_error(http_exception)
+            end
+
+          end
+
+          context "when the node exists" do
+
+            it "falls back to saving without policyfile attributes" do
+              expect(@rest).to receive(:put_rest).with("nodes/example-node", node.for_json).and_raise(http_exception)
+              expect(@rest).to receive(:put_rest).with("nodes/example-node", trimmed_node).and_return(@node)
+              expect { node.save }.to_not raise_error
+            end
+
+          end
+
+          context "when the node doesn't exist" do
+
+            let(:response_404) do
+              Net::HTTPResponse.send(:response_class, "404").new("1.0", "404", "Not Found")
+            end
+
+            let(:http_exception_404) do
+              begin
+                response_404.error!
+              rescue => e
+                e
+              end
+            end
+
+            it "falls back to saving without policyfile attributes" do
+              expect(@rest).to receive(:put_rest).with("nodes/example-node", node.for_json).and_raise(http_exception)
+              expect(@rest).to receive(:put_rest).with("nodes/example-node", trimmed_node).and_raise(http_exception_404)
+              expect(@rest).to receive(:post_rest).with("nodes", trimmed_node).and_return(@node)
+              node.save
+            end
+
+            it "creates the node without policyfile attributes" do
+              expect(@rest).to receive(:post_rest).with("nodes", node.for_json).and_raise(http_exception)
+              expect(@rest).to receive(:post_rest).with("nodes", trimmed_node).and_return(@node)
+              node.create
+            end
+          end
+
+        end
+
+      end
+
     end
   end
 
diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb
index 1dbd07a..d18b6f7 100644
--- a/spec/unit/platform/query_helpers_spec.rb
+++ b/spec/unit/platform/query_helpers_spec.rb
@@ -20,8 +20,8 @@ require 'spec_helper'
 
 describe "Chef::Platform#windows_server_2003?" do
   it "returns false early when not on windows" do
-    allow(Chef::Platform).to receive(:windows?).and_return(false)
-    expect(Chef::Platform).not_to receive(:require) 
+    allow(ChefConfig).to receive(:windows?).and_return(false)
+    expect(Chef::Platform).not_to receive(:require)
     expect(Chef::Platform.windows_server_2003?).to be_falsey
   end
 
@@ -31,7 +31,127 @@ describe "Chef::Platform#windows_server_2003?" do
   end
 end
 
-describe 'Chef::Platform#supports_dsc?' do 
+describe "Chef::Platform#windows_nano_server?" do
+  include_context "Win32"
+
+  let(:key) { "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels" }
+  let(:key_query_value) { 0x0001 }
+  let(:access) { key_query_value | 0x0100 }
+  let(:hive) { double("Win32::Registry::HKEY_LOCAL_MACHINE") }
+  let(:registry) { double("Win32::Registry") }
+
+  before(:all) do
+    Win32::Registry = Class.new
+    Win32::Registry::Error = Class.new(RuntimeError)
+  end
+
+  before do
+    Win32::Registry::HKEY_LOCAL_MACHINE = hive
+    Win32::Registry::KEY_QUERY_VALUE = key_query_value
+  end
+
+  after do
+    Win32::Registry.send(:remove_const, 'HKEY_LOCAL_MACHINE') if defined?(Win32::Registry::HKEY_LOCAL_MACHINE)
+    Win32::Registry.send(:remove_const, 'KEY_QUERY_VALUE') if defined?(Win32::Registry::KEY_QUERY_VALUE)
+  end
+
+  it "returns false early when not on windows" do
+    allow(ChefConfig).to receive(:windows?).and_return(false)
+    expect(Chef::Platform).to_not receive(:require)
+    expect(Chef::Platform.windows_nano_server?).to be false
+  end
+
+  it "returns true when the registry value is 1" do
+    allow(ChefConfig).to receive(:windows?).and_return(true)
+    allow(Chef::Platform).to receive(:require).with('win32/registry')
+    expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).
+      with(key, access).
+      and_yield(registry)
+    expect(registry).to receive(:[]).with("NanoServer").and_return(1)
+    expect(Chef::Platform.windows_nano_server?).to be true
+  end
+
+  it "returns false when the registry value is not 1" do
+    allow(ChefConfig).to receive(:windows?).and_return(true)
+    allow(Chef::Platform).to receive(:require).with('win32/registry')
+    expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).
+      with(key, access).
+      and_yield(registry)
+    expect(registry).to receive(:[]).with("NanoServer").and_return(0)
+    expect(Chef::Platform.windows_nano_server?).to be false
+  end
+
+  it "returns false when the registry value does not exist" do
+    allow(ChefConfig).to receive(:windows?).and_return(true)
+    allow(Chef::Platform).to receive(:require).with('win32/registry')
+    expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).
+      with(key, access).
+      and_yield(registry)
+    expect(registry).to receive(:[]).with("NanoServer").
+      and_raise(Win32::Registry::Error, "The system cannot find the file specified.")
+    expect(Chef::Platform.windows_nano_server?).to be false
+  end
+
+  it "returns false when the registry key does not exist" do
+    allow(ChefConfig).to receive(:windows?).and_return(true)
+    allow(Chef::Platform).to receive(:require).with('win32/registry')
+    expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).
+      with(key, access).
+      and_raise(Win32::Registry::Error, "The system cannot find the file specified.")
+    expect(Chef::Platform.windows_nano_server?).to be false
+  end
+end
+
+describe "Chef::Platform#supports_msi?" do
+  include_context "Win32"  # clear and restore Win32:: namespace
+
+  let(:key) { "System\\CurrentControlSet\\Services\\msiserver" }
+  let(:key_query_value) { 0x0001 }
+  let(:access) { key_query_value }
+  let(:hive) { double("Win32::Registry::HKEY_LOCAL_MACHINE") }
+  let(:registry) { double("Win32::Registry") }
+
+  before(:all) do
+    Win32::Registry = Class.new
+    Win32::Registry::Error = Class.new(RuntimeError)
+  end
+
+  before do
+    Win32::Registry::HKEY_LOCAL_MACHINE = hive
+    Win32::Registry::KEY_QUERY_VALUE = key_query_value
+  end
+
+  after do
+    Win32::Registry.send(:remove_const, 'HKEY_LOCAL_MACHINE') if defined?(Win32::Registry::HKEY_LOCAL_MACHINE)
+    Win32::Registry.send(:remove_const, 'KEY_QUERY_VALUE') if defined?(Win32::Registry::KEY_QUERY_VALUE)
+  end
+
+  it "returns false early when not on windows" do
+    allow(ChefConfig).to receive(:windows?).and_return(false)
+    expect(Chef::Platform).to_not receive(:require)
+    expect(Chef::Platform.supports_msi?).to be false
+  end
+
+  it "returns true when the registry key exists" do
+    allow(ChefConfig).to receive(:windows?).and_return(true)
+    allow(Chef::Platform).to receive(:require).with('win32/registry')
+    expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).
+      with(key, access).
+      and_yield(registry)
+    expect(Chef::Platform.supports_msi?).to be true
+  end
+
+  it "returns false when the registry key does not exist" do
+    allow(ChefConfig).to receive(:windows?).and_return(true)
+    allow(Chef::Platform).to receive(:require).with('win32/registry')
+    expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).
+      with(key, access).
+      and_raise(Win32::Registry::Error, "The system cannot find the file specified.")
+    expect(Chef::Platform.supports_msi?).to be false
+  end
+end
+
+describe 'Chef::Platform#supports_dsc?' do
   it 'returns false if powershell is not present' do
     node = Chef::Node.new
     expect(Chef::Platform.supports_dsc?(node)).to be_falsey
@@ -54,7 +174,7 @@ describe 'Chef::Platform#supports_dsc?' do
   end
 end
 
-describe 'Chef::Platform#supports_dsc_invoke_resource?' do 
+describe 'Chef::Platform#supports_dsc_invoke_resource?' do
   it 'returns false if powershell is not present' do
     node = Chef::Node.new
     expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_falsey
@@ -75,3 +195,26 @@ describe 'Chef::Platform#supports_dsc_invoke_resource?' do
   end
 end
 
+describe 'Chef::Platform#dsc_refresh_mode_disabled?' do
+  let(:node) { instance_double('Chef::Node') }
+  let(:cmdlet) { instance_double('Chef::Util::Powershell::Cmdlet') }
+  let(:cmdlet_result) { instance_double('Chef::Util::Powershell::CmdletResult')}
+
+  it "returns true when RefreshMode is Disabled" do
+    expect(Chef::Util::Powershell::Cmdlet).to receive(:new).
+      with(node, "Get-DscLocalConfigurationManager", :object).
+      and_return(cmdlet)
+    expect(cmdlet).to receive(:run!).and_return(cmdlet_result)
+    expect(cmdlet_result).to receive(:return_value).and_return({ 'RefreshMode' => 'Disabled' })
+    expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be true
+  end
+
+  it "returns false when RefreshMode is not Disabled" do
+    expect(Chef::Util::Powershell::Cmdlet).to receive(:new).
+      with(node, "Get-DscLocalConfigurationManager", :object).
+      and_return(cmdlet)
+    expect(cmdlet).to receive(:run!).and_return(cmdlet_result)
+    expect(cmdlet_result).to receive(:return_value).and_return({ 'RefreshMode' => 'LaLaLa' })
+    expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be false
+  end
+end
diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb
index e0115bc..34b46f6 100644
--- a/spec/unit/platform_spec.rb
+++ b/spec/unit/platform_spec.rb
@@ -18,29 +18,6 @@
 
 require 'spec_helper'
 
-describe "Chef::Platform supports" do
-  [
-    :freebsd,
-    :ubuntu,
-    :debian,
-    :centos,
-    :fedora,
-    :suse,
-    :opensuse,
-    :redhat,
-    :oracle,
-    :gentoo,
-    :arch,
-    :solaris,
-    :gcel,
-    :ibm_powerkvm
-  ].each do |platform|
-    it "#{platform}" do
-      expect(Chef::Platform.platforms).to have_key(platform)
-    end
-  end
-end
-
 describe Chef::Platform do
 
   context "while testing with fake data" do
@@ -126,7 +103,7 @@ describe Chef::Platform do
     end
 
     it "should raise an exception if a provider cannot be found for a resource type" do
-      expect { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.to raise_error(ArgumentError)
+      expect { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.to raise_error(Chef::Exceptions::ProviderNotFound)
     end
 
     it "should look up a provider for a resource with a Chef::Resource object" do
@@ -261,41 +238,4 @@ describe Chef::Platform do
 
   end
 
-  context "while testing the configured platform data" do
-
-    it "should use the solaris package provider on Solaris <11" do
-      pmap = Chef::Platform.find("Solaris2", "5.9")
-      expect(pmap[:package]).to eql(Chef::Provider::Package::Solaris)
-    end
-
-    it "should use the IPS package provider on Solaris 11" do
-      pmap = Chef::Platform.find("Solaris2", "5.11")
-      expect(pmap[:package]).to eql(Chef::Provider::Package::Ips)
-    end
-
-    it "should use the Redhat service provider on SLES11" do
-      1.upto(3) do |sp|
-        pmap = Chef::Platform.find("SUSE", "11.#{sp}")
-        expect(pmap[:service]).to eql(Chef::Provider::Service::Redhat)
-      end
-    end
-
-    it "should use the Systemd service provider on SLES12" do
-      pmap = Chef::Platform.find("SUSE", "12.0")
-      expect(pmap[:service]).to eql(Chef::Provider::Service::Systemd)
-    end
-
-    it "should use the SUSE group provider on SLES11" do
-      1.upto(3) do |sp|
-        pmap = Chef::Platform.find("SUSE", "11.#{sp}")
-        expect(pmap[:group]).to eql(Chef::Provider::Group::Suse)
-      end
-    end
-
-    it "should use the Gpasswd group provider on SLES12" do
-      pmap = Chef::Platform.find("SUSE", "12.0")
-      expect(pmap[:group]).to eql(Chef::Provider::Group::Gpasswd)
-    end
-  end
-
 end
diff --git a/spec/unit/policy_builder/dynamic_spec.rb b/spec/unit/policy_builder/dynamic_spec.rb
new file mode 100644
index 0000000..aff19f4
--- /dev/null
+++ b/spec/unit/policy_builder/dynamic_spec.rb
@@ -0,0 +1,275 @@
+#
+# Author:: Daniel DeLeo (<dan at getchef.com>)
+# Copyright:: Copyright 2014 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 'spec_helper'
+require 'chef/policy_builder'
+
+describe Chef::PolicyBuilder::Dynamic do
+
+  let(:node_name) { "joe_node" }
+  let(:ohai_data) { {"platform" => "ubuntu", "platform_version" => "13.04", "fqdn" => "joenode.example.com"} }
+  let(:json_attribs) { {"custom_attr" => "custom_attr_value"} }
+  let(:override_runlist) { nil }
+  let(:events) { Chef::EventDispatch::Dispatcher.new }
+
+  let(:err_namespace) { Chef::PolicyBuilder::Policyfile }
+
+  let(:base_node) do
+    node = Chef::Node.new
+    node.name(node_name)
+    node
+  end
+
+  let(:node) { base_node }
+
+  subject(:policy_builder) { Chef::PolicyBuilder::Dynamic.new(node_name, ohai_data, json_attribs, override_runlist, events) }
+
+  describe "loading policy data" do
+
+    describe "delegating PolicyBuilder API to the correct implementation" do
+
+      let(:implementation) { instance_double("Chef::PolicyBuilder::Policyfile") }
+
+      before do
+        allow(policy_builder).to receive(:implementation).and_return(implementation)
+      end
+
+      # Dynamic should load_node, figure out the correct backend, then forward
+      # messages to it after. That behavior is tested below.
+      it "responds to #load_node" do
+        expect(policy_builder).to respond_to(:load_node)
+      end
+
+      it "forwards #original_runlist" do
+        expect(implementation).to receive(:original_runlist)
+        policy_builder.original_runlist
+      end
+
+      it "forwards #run_context" do
+        expect(implementation).to receive(:run_context)
+        policy_builder.run_context
+      end
+
+      it "forwards #run_list_expansion" do
+        expect(implementation).to receive(:run_list_expansion)
+        policy_builder.run_list_expansion
+      end
+
+      it "forwards #build_node to the implementation object" do
+        expect(implementation).to receive(:build_node)
+        policy_builder.build_node
+      end
+
+      it "forwards #setup_run_context to the implementation object" do
+        expect(implementation).to receive(:setup_run_context)
+        policy_builder.setup_run_context
+
+        arg = Object.new
+
+        expect(implementation).to receive(:setup_run_context).with(arg)
+        policy_builder.setup_run_context(arg)
+      end
+
+      it "forwards #expand_run_list to the implementation object" do
+        expect(implementation).to receive(:expand_run_list)
+        policy_builder.expand_run_list
+      end
+
+      it "forwards #sync_cookbooks to the implementation object" do
+        expect(implementation).to receive(:sync_cookbooks)
+        policy_builder.sync_cookbooks
+      end
+
+      it "forwards #temporary_policy? to the implementation object" do
+        expect(implementation).to receive(:temporary_policy?)
+        policy_builder.temporary_policy?
+      end
+
+    end
+
+    describe "selecting a backend implementation" do
+
+      let(:implementation) do
+        policy_builder.select_implementation(node)
+        policy_builder.implementation
+      end
+
+      context "when no policyfile attributes are present on the node" do
+
+        context "and json_attribs are not given" do
+
+          let(:json_attribs) { {} }
+
+          it "uses the ExpandNodeObject implementation" do
+            expect(implementation).to be_a(Chef::PolicyBuilder::ExpandNodeObject)
+          end
+
+        end
+
+        context "and no policyfile attributes are present in json_attribs" do
+
+          let(:json_attribs) { {"foo" => "bar"} }
+
+          it "uses the ExpandNodeObject implementation" do
+            expect(implementation).to be_a(Chef::PolicyBuilder::ExpandNodeObject)
+          end
+
+        end
+
+        context "and :use_policyfile is set in Chef::Config" do
+
+          before do
+            Chef::Config[:use_policyfile] = true
+          end
+
+          it "uses the Policyfile implementation" do
+            expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile)
+          end
+
+        end
+
+        context "and policy_name and policy_group are set on Chef::Config" do
+
+          before do
+            Chef::Config[:policy_name] = "example-policy"
+            Chef::Config[:policy_group] = "testing"
+          end
+
+          it "uses the Policyfile implementation" do
+            expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile)
+          end
+
+        end
+
+        context "and deployment_group and policy_document_native_api are set on Chef::Config" do
+
+          before do
+            Chef::Config[:deployment_group] = "example-policy-staging"
+            Chef::Config[:policy_document_native_api] = false
+          end
+
+          it "uses the Policyfile implementation" do
+            expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile)
+          end
+
+        end
+
+        context "and policyfile attributes are present in json_attribs" do
+
+          let(:json_attribs) { {"policy_name" => "example-policy", "policy_group" => "testing"} }
+
+          it "uses the Policyfile implementation" do
+            expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile)
+          end
+
+        end
+
+      end
+
+      context "when policyfile attributes are present on the node" do
+
+        let(:node) do
+          base_node.policy_name = "example-policy"
+          base_node.policy_group = "staging"
+          base_node
+        end
+
+        it "uses the Policyfile implementation" do
+          expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile)
+        end
+
+      end
+
+    end
+
+    describe "loading a node" do
+
+      let(:implementation) { instance_double("Chef::PolicyBuilder::Policyfile") }
+
+      before do
+        allow(policy_builder).to receive(:implementation).and_return(implementation)
+      end
+
+      context "when not running chef solo" do
+
+
+        context "when successful" do
+
+          before do
+            expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
+            expect(policy_builder).to receive(:select_implementation).with(node)
+            expect(implementation).to receive(:finish_load_node).with(node)
+          end
+
+          it "selects the backend implementation and continues node loading" do
+            policy_builder.load_node
+          end
+
+        end
+
+        context "when an error occurs finding the node" do
+
+          before do
+            expect(Chef::Node).to receive(:find_or_create).with(node_name).and_raise("oops")
+          end
+
+          it "sends a node_load_failed event and re-raises" do
+            expect(events).to receive(:node_load_failed)
+            expect { policy_builder.load_node }.to raise_error("oops")
+          end
+
+        end
+
+        context "when an error occurs in the implementation's finish_load_node call" do
+
+          before do
+            expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
+            expect(policy_builder).to receive(:select_implementation).with(node)
+            expect(implementation).to receive(:finish_load_node).and_raise("oops")
+          end
+
+
+          it "sends a node_load_failed event and re-raises" do
+            expect(events).to receive(:node_load_failed)
+            expect { policy_builder.load_node }.to raise_error("oops")
+          end
+
+        end
+
+      end
+
+      context "when running chef solo" do
+
+        before do
+          Chef::Config[:solo] = true
+          expect(Chef::Node).to receive(:build).with(node_name).and_return(node)
+          expect(policy_builder).to receive(:select_implementation).with(node)
+          expect(implementation).to receive(:finish_load_node).with(node)
+        end
+
+        it "selects the backend implementation and continues node loading" do
+          policy_builder.load_node
+        end
+
+      end
+
+    end
+
+  end
+
+end
diff --git a/spec/unit/policy_builder/expand_node_object_spec.rb b/spec/unit/policy_builder/expand_node_object_spec.rb
index 8e9fdc3..306d677 100644
--- a/spec/unit/policy_builder/expand_node_object_spec.rb
+++ b/spec/unit/policy_builder/expand_node_object_spec.rb
@@ -34,10 +34,18 @@ describe Chef::PolicyBuilder::ExpandNodeObject do
       expect(policy_builder).to respond_to(:node)
     end
 
-    it "implements a load_node method" do
+    it "implements a load_node method for backwards compatibility until Chef 13" do
       expect(policy_builder).to respond_to(:load_node)
     end
 
+    it "has removed the deprecated #load_node method", :chef_gte_13_only do
+      expect(policy_builder).to_not respond_to(:load_node)
+    end
+
+    it "implements a finish_load_node method" do
+      expect(policy_builder).to respond_to(:finish_load_node)
+    end
+
     it "implements  a build_node method" do
       expect(policy_builder).to respond_to(:build_node)
     end
@@ -63,39 +71,13 @@ describe Chef::PolicyBuilder::ExpandNodeObject do
       expect(policy_builder).to respond_to(:temporary_policy?)
     end
 
-    describe "loading the node" do
-
-      context "on chef-solo" do
-
-        before do
-          Chef::Config[:solo] = true
-        end
-
-        it "creates a new in-memory node object with the given name" do
-          policy_builder.load_node
-          expect(policy_builder.node.name).to eq(node_name)
-        end
-
-      end
-
-      context "on chef-client" do
-
-        let(:node) { Chef::Node.new.tap { |n| n.name(node_name) } }
-
-        it "loads or creates a node on the server" do
-          expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
-          policy_builder.load_node
-          expect(policy_builder.node).to eq(node)
-        end
+    describe "finishing loading the node" do
 
-      end
-    end
-
-    describe "building the node" do
+      let(:node) { Chef::Node.new.tap { |n| n.name(node_name) } }
 
-      # XXX: Chef::Client just needs to be able to call this, it doesn't depend on the return value.
-      it "builds the node and returns the updated node object" do
-        skip
+      it "stores the node" do
+        policy_builder.finish_load_node(node)
+        expect(policy_builder.node).to eq(node)
       end
 
     end
@@ -124,7 +106,8 @@ describe Chef::PolicyBuilder::ExpandNodeObject do
 
   end
 
-  context "once the node has been loaded" do
+  context "deprecated #load_node method" do
+
     let(:node) do
       node = Chef::Node.new
       node.name(node_name)
@@ -133,10 +116,29 @@ describe Chef::PolicyBuilder::ExpandNodeObject do
     end
 
     before do
+      Chef::Config[:treat_deprecation_warnings_as_errors] = false
       expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
       policy_builder.load_node
     end
 
+    it "loads the node" do
+      expect(policy_builder.node).to eq(node)
+    end
+
+  end
+
+  context "once the node has been loaded" do
+    let(:node) do
+      node = Chef::Node.new
+      node.name(node_name)
+      node.run_list(["recipe[a::default]", "recipe[b::server]"])
+      node
+    end
+
+    before do
+      policy_builder.finish_load_node(node)
+    end
+
     it "expands the run_list" do
       expect(policy_builder.expand_run_list).to be_a(Chef::RunList::RunListExpansion)
       expect(policy_builder.run_list_expansion).to be_a(Chef::RunList::RunListExpansion)
@@ -167,8 +169,7 @@ describe Chef::PolicyBuilder::ExpandNodeObject do
 
     before do
       Chef::Config[:environment] = configured_environment
-      expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
-      policy_builder.load_node
+      policy_builder.finish_load_node(node)
       policy_builder.build_node
     end
 
@@ -302,11 +303,9 @@ describe Chef::PolicyBuilder::ExpandNodeObject do
     let(:cookbook_synchronizer) { double("CookbookSynchronizer") }
 
     before do
-      expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
-
       allow(policy_builder).to receive(:api_service).and_return(chef_http)
 
-      policy_builder.load_node
+      policy_builder.finish_load_node(node)
       policy_builder.build_node
 
       run_list_expansion = policy_builder.run_list_expansion
diff --git a/spec/unit/policy_builder/policyfile_spec.rb b/spec/unit/policy_builder/policyfile_spec.rb
index e4f7388..ed89326 100644
--- a/spec/unit/policy_builder/policyfile_spec.rb
+++ b/spec/unit/policy_builder/policyfile_spec.rb
@@ -76,8 +76,11 @@ describe Chef::PolicyBuilder::Policyfile do
 
   let(:policyfile_run_list) { ["recipe[example1::default]", "recipe[example2::server]"] }
 
-  let(:parsed_policyfile_json) do
+  let(:basic_valid_policy_data) do
     {
+      "name" => "example-policy",
+      "revision_id" => "123abc",
+
       "run_list" => policyfile_run_list,
 
       "cookbook_locks" => {
@@ -90,6 +93,8 @@ describe Chef::PolicyBuilder::Policyfile do
     }
   end
 
+  let(:parsed_policyfile_json) { basic_valid_policy_data }
+
   let(:err_namespace) { Chef::PolicyBuilder::Policyfile }
 
   it "configures a Chef HTTP API client" do
@@ -166,30 +171,28 @@ describe Chef::PolicyBuilder::Policyfile do
     end
 
     before do
-      # TODO: agree on this name and logic.
+      Chef::Config[:policy_document_native_api] = false
       Chef::Config[:deployment_group] = "example-policy-stage"
       allow(policy_builder).to receive(:http_api).and_return(http_api)
     end
 
     describe "when using compatibility mode (policy_document_native_api == false)" do
 
+      before do
+        Chef::Config[:deployment_group] = "example-policy-stage"
+      end
+
       context "when the deployment group cannot be loaded" do
         let(:error404) { Net::HTTPServerException.new("404 message", :body) }
 
         before do
-          expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
           expect(http_api).to receive(:get).
             with("data/policyfiles/example-policy-stage").
             and_raise(error404)
         end
 
         it "raises an error" do
-          expect { policy_builder.load_node }.to raise_error(err_namespace::ConfigurationError)
-        end
-
-        it "sends error message to the event system" do
-          expect(events).to receive(:node_load_failed).with(node_name, an_instance_of(err_namespace::ConfigurationError), Chef::Config)
-          expect { policy_builder.load_node }.to raise_error(err_namespace::ConfigurationError)
+          expect { policy_builder.finish_load_node(node) }.to raise_error(err_namespace::ConfigurationError)
         end
 
       end
@@ -197,20 +200,12 @@ describe Chef::PolicyBuilder::Policyfile do
       context "when the deployment_group is not configured" do
         before do
           Chef::Config[:deployment_group] = nil
-          expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
         end
 
         it "errors while loading the node" do
-          expect { policy_builder.load_node }.to raise_error(err_namespace::ConfigurationError)
+          expect { policy_builder.finish_load_node(node) }.to raise_error(err_namespace::ConfigurationError)
         end
 
-
-        it "passes error information to the event system" do
-          # TODO: also make sure something acceptable happens with the error formatters
-          err_class = err_namespace::ConfigurationError
-          expect(events).to receive(:node_load_failed).with(node_name, an_instance_of(err_class), Chef::Config)
-          expect { policy_builder.load_node }.to raise_error(err_class)
-        end
       end
 
       context "when deployment_group is correctly configured" do
@@ -303,8 +298,7 @@ describe Chef::PolicyBuilder::Policyfile do
       end
 
       it "implements #expand_run_list in a manner compatible with ExpandNodeObject" do
-        expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
-        policy_builder.load_node
+        policy_builder.finish_load_node(node)
         expect(policy_builder.expand_run_list).to respond_to(:recipes)
         expect(policy_builder.expand_run_list.recipes).to eq(["example1::default", "example2::server"])
         expect(policy_builder.expand_run_list.roles).to eq([])
@@ -341,46 +335,261 @@ describe Chef::PolicyBuilder::Policyfile do
 
       describe "building the node object" do
 
+        let(:extra_chef_config) { {} }
+
         before do
-          expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
+          # must be set before #build_node is called to have the proper effect
+          extra_chef_config.each do |key, value|
+            Chef::Config[key] = value
+          end
 
-          policy_builder.load_node
+          policy_builder.finish_load_node(node)
           policy_builder.build_node
         end
 
+        # it sets policy_name and policy_group in the following priority order:
+        # -j JSON > config file > node object
+
+        describe "selecting policy_name and policy_group from the various sources" do
+
+          context "when only set in node JSON" do
+
+            let(:json_attribs) do
+              {
+                "policy_name" => "policy_name_from_node_json",
+                "policy_group" => "policy_group_from_node_json"
+              }
+            end
+
+            it "sets policy_name and policy_group on Chef::Config" do
+              expect(Chef::Config[:policy_name]).to eq("policy_name_from_node_json")
+              expect(Chef::Config[:policy_group]).to eq("policy_group_from_node_json")
+            end
+
+            it "sets policy_name and policy_group on the node object" do
+              expect(node.policy_name).to eq("policy_name_from_node_json")
+              expect(node.policy_group).to eq("policy_group_from_node_json")
+            end
+
+          end
+
+          context "when only set in Chef::Config" do
+
+            let(:extra_chef_config) do
+              {
+                policy_name: "policy_name_from_config",
+                policy_group: "policy_group_from_config"
+              }
+            end
+
+            it "sets policy_name and policy_group on the node object" do
+              expect(node.policy_name).to eq("policy_name_from_config")
+              expect(node.policy_group).to eq("policy_group_from_config")
+            end
+
+          end
+
+          context "when only set on the node" do
+
+            let(:node) do
+              node = Chef::Node.new
+              node.name(node_name)
+              node.policy_name = "policy_name_from_node"
+              node.policy_group = "policy_group_from_node"
+              node
+            end
+
+            it "sets policy_name and policy_group on Chef::Config" do
+              expect(Chef::Config[:policy_name]).to eq("policy_name_from_node")
+              expect(Chef::Config[:policy_group]).to eq("policy_group_from_node")
+            end
+
+          end
+
+          context "when set in Chef::Config and the fetched node" do
+
+            let(:node) do
+              node = Chef::Node.new
+              node.name(node_name)
+              node.policy_name = "policy_name_from_node"
+              node.policy_group = "policy_group_from_node"
+              node
+            end
+
+            let(:extra_chef_config) do
+              {
+                policy_name: "policy_name_from_config",
+                policy_group: "policy_group_from_config"
+              }
+            end
+
+            it "prefers the policy_name and policy_group from Chef::Config" do
+              expect(node.policy_name).to eq("policy_name_from_config")
+              expect(node.policy_group).to eq("policy_group_from_config")
+            end
+
+          end
+
+          context "when set in node json and the fetched node" do
+
+            let(:json_attribs) do
+              {
+                "policy_name" => "policy_name_from_node_json",
+                "policy_group" => "policy_group_from_node_json"
+              }
+            end
+
+            let(:node) do
+              node = Chef::Node.new
+              node.name(node_name)
+              node.policy_name = "policy_name_from_node"
+              node.policy_group = "policy_group_from_node"
+              node
+            end
+
+
+            it "prefers the policy_name and policy_group from the node json" do
+              expect(policy_builder.policy_name).to eq("policy_name_from_node_json")
+              expect(policy_builder.policy_group).to eq("policy_group_from_node_json")
+
+              expect(Chef::Config[:policy_name]).to eq("policy_name_from_node_json")
+              expect(Chef::Config[:policy_group]).to eq("policy_group_from_node_json")
+              expect(node.policy_name).to eq("policy_name_from_node_json")
+              expect(node.policy_group).to eq("policy_group_from_node_json")
+            end
+
+          end
+
+          context "when set in all sources" do
+
+            let(:json_attribs) do
+              {
+                "policy_name" => "policy_name_from_node_json",
+                "policy_group" => "policy_group_from_node_json"
+              }
+            end
+
+            let(:node) do
+              node = Chef::Node.new
+              node.name(node_name)
+              node.policy_name = "policy_name_from_node"
+              node.policy_group = "policy_group_from_node"
+              node
+            end
+
+            let(:extra_chef_config) do
+              {
+                policy_name: "policy_name_from_config",
+                policy_group: "policy_group_from_config"
+              }
+            end
+
+            it "prefers the policy_name and group from node json" do
+              expect(policy_builder.policy_name).to eq("policy_name_from_node_json")
+              expect(policy_builder.policy_group).to eq("policy_group_from_node_json")
+
+              expect(Chef::Config[:policy_name]).to eq("policy_name_from_node_json")
+              expect(Chef::Config[:policy_group]).to eq("policy_group_from_node_json")
+              expect(node.policy_name).to eq("policy_name_from_node_json")
+              expect(node.policy_group).to eq("policy_group_from_node_json")
+            end
+
+          end
+
+        end
+
         it "resets default and override data" do
           expect(node["default_key"]).to be_nil
           expect(node["override_key"]).to be_nil
         end
 
-        it "applies ohai data" do
-          expect(ohai_data).to_not be_empty # ensure test is testing something
-          ohai_data.each do |key, value|
-            expect(node.automatic_attrs[key]).to eq(value)
+        describe "setting attribute values" do
+
+          before do
+            policy_builder.build_node
           end
-        end
 
-        it "applies attributes from json file" do
-          expect(node["custom_attr"]).to eq("custom_attr_value")
-        end
+          it "resets default and override data" do
+            expect(node["default_key"]).to be_nil
+            expect(node["override_key"]).to be_nil
+          end
 
-        it "applies attributes from the policyfile" do
-          expect(node["policyfile_default_attr"]).to eq("policyfile_default_value")
-          expect(node["policyfile_override_attr"]).to eq("policyfile_override_value")
-        end
+          it "applies ohai data" do
+            expect(ohai_data).to_not be_empty # ensure test is testing something
+            ohai_data.each do |key, value|
+              expect(node.automatic_attrs[key]).to eq(value)
+            end
+          end
 
-        it "sets the policyfile's run_list on the node object" do
-          expect(node.run_list).to eq(policyfile_run_list)
-        end
+          it "applies attributes from json file" do
+            expect(node["custom_attr"]).to eq("custom_attr_value")
+          end
 
-        it "creates node.automatic_attrs[:roles]" do
-          expect(node.automatic_attrs[:roles]).to eq([])
-        end
+          it "applies attributes from the policyfile" do
+            expect(node["policyfile_default_attr"]).to eq("policyfile_default_value")
+            expect(node["policyfile_override_attr"]).to eq("policyfile_override_value")
+          end
 
-        it "create node.automatic_attrs[:recipes]" do
-          expect(node.automatic_attrs[:recipes]).to eq(["example1::default", "example2::server"])
+          it "sets the policyfile's run_list on the node object" do
+            expect(node.run_list).to eq(policyfile_run_list)
+          end
+
+          it "creates node.automatic_attrs[:roles]" do
+            expect(node.automatic_attrs[:roles]).to eq([])
+          end
+
+          it "create node.automatic_attrs[:recipes]" do
+            expect(node.automatic_attrs[:recipes]).to eq(["example1::default", "example2::server"])
+          end
         end
 
+        context "when a named run_list is given" do
+
+          before do
+            Chef::Config[:named_run_list] = "deploy-app"
+          end
+
+          context "and the named run_list is not present in the policy" do
+
+            it "raises a ConfigurationError" do
+              err_class = Chef::PolicyBuilder::Policyfile::ConfigurationError
+              err_text = "Policy 'example-policy' revision '123abc' does not have named_run_list 'deploy-app'(available named_run_lists: [])"
+              expect { policy_builder.build_node }.to raise_error(err_class, err_text)
+            end
+
+          end
+
+          context "and the named run_list is present in the policy" do
+
+            let(:parsed_policyfile_json) do
+              basic_valid_policy_data.dup.tap do |p|
+                p["named_run_lists"] = {
+                  "deploy-app" => [ "recipe[example1::default]" ]
+                }
+              end
+            end
+
+            before do
+              policy_builder.build_node
+            end
+
+            it "sets the run list to the desired named run list" do
+              expect(policy_builder.run_list).to eq([ "recipe[example1::default]" ])
+              expected_expansion = Chef::PolicyBuilder::Policyfile::RunListExpansionIsh.new([ "example1::default" ], [])
+              expect(policy_builder.run_list_expansion).to eq(expected_expansion)
+              expect(policy_builder.run_list_with_versions_for_display).to eq(["example1::default at 2.3.5 (168d210)"])
+              expect(node.run_list).to eq([ Chef::RunList::RunListItem.new("recipe[example1::default]") ])
+              expect(node[:roles]).to eq( [] )
+              expect(node[:recipes]).to eq( ["example1::default"] )
+            end
+
+            it "disables the cookbook cache cleaner" do
+              expect(Chef::CookbookCacheCleaner.instance.skip_removal).to be(true)
+            end
+
+          end
+
+        end
       end
 
 
@@ -389,8 +598,8 @@ describe Chef::PolicyBuilder::Policyfile do
         let(:example1_cookbook_data) { double("CookbookVersion Hash for example1 cookbook") }
         let(:example2_cookbook_data) { double("CookbookVersion Hash for example2 cookbook") }
 
-        let(:example1_cookbook_object) { double("Chef::CookbookVersion for example1 cookbook") }
-        let(:example2_cookbook_object) { double("Chef::CookbookVersion for example2 cookbook") }
+        let(:example1_cookbook_object) { double("Chef::CookbookVersion for example1 cookbook", version: "0.1.2") }
+        let(:example2_cookbook_object) { double("Chef::CookbookVersion for example2 cookbook", version: "1.2.3") }
 
         let(:expected_cookbook_hash) do
           { "example1" => example1_cookbook_object, "example2" => example2_cookbook_object }
@@ -410,9 +619,7 @@ describe Chef::PolicyBuilder::Policyfile do
             let(:error404) { Net::HTTPServerException.new("404 message", :body) }
 
             before do
-              expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
-
-              policy_builder.load_node
+              policy_builder.finish_load_node(node)
               policy_builder.build_node
 
               expect(http_api).to receive(:get).with(cookbook1_url).
@@ -429,9 +636,9 @@ describe Chef::PolicyBuilder::Policyfile do
         shared_examples_for "fetching cookbooks when they exist" do
           context "and the cookbooks can be fetched" do
             before do
-              expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
+              Chef.reset!
 
-              policy_builder.load_node
+              policy_builder.finish_load_node(node)
               policy_builder.build_node
 
               allow(Chef::CookbookSynchronizer).to receive(:new).
@@ -439,6 +646,10 @@ describe Chef::PolicyBuilder::Policyfile do
                 and_return(cookbook_synchronizer)
             end
 
+            after do
+              Chef.reset!
+            end
+
             it "builds a Hash of the form 'cookbook_name' => Chef::CookbookVersion" do
               expect(policy_builder.cookbooks_to_sync).to eq(expected_cookbook_hash)
             end
@@ -451,11 +662,20 @@ describe Chef::PolicyBuilder::Policyfile do
             it "builds a run context" do
               expect(cookbook_synchronizer).to receive(:sync_cookbooks)
               expect_any_instance_of(Chef::RunContext).to receive(:load).with(policy_builder.run_list_expansion_ish)
+              expect_any_instance_of(Chef::CookbookCollection).to receive(:validate!)
               run_context = policy_builder.setup_run_context
               expect(run_context.node).to eq(node)
               expect(run_context.cookbook_collection.keys).to match_array(["example1", "example2"])
             end
 
+            it "makes the run context available via static method on Chef" do
+              expect(cookbook_synchronizer).to receive(:sync_cookbooks)
+              expect_any_instance_of(Chef::RunContext).to receive(:load).with(policy_builder.run_list_expansion_ish)
+              expect_any_instance_of(Chef::CookbookCollection).to receive(:validate!)
+              run_context = policy_builder.setup_run_context
+              expect(Chef.run_context).to eq(run_context)
+            end
+
           end
         end # shared_examples_for "fetching cookbooks"
 
diff --git a/spec/unit/property/state_spec.rb b/spec/unit/property/state_spec.rb
new file mode 100644
index 0000000..e7fee03
--- /dev/null
+++ b/spec/unit/property/state_spec.rb
@@ -0,0 +1,506 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource#identity and #state" do
+  include IntegrationSupport
+
+  class NewResourceNamer
+    @i = 0
+    def self.next
+      "chef_resource_property_spec_#{@i += 1}"
+    end
+  end
+
+  def self.new_resource_name
+    NewResourceNamer.next
+  end
+
+  let(:resource_class) do
+    new_resource_name = self.class.new_resource_name
+    Class.new(Chef::Resource) do
+      resource_name new_resource_name
+    end
+  end
+
+  let(:resource) do
+    resource_class.new("blah")
+  end
+
+  def self.english_join(values)
+    return '<nothing>' if values.size == 0
+    return values[0].inspect if values.size == 1
+    "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}"
+  end
+
+  def self.with_property(*properties, &block)
+    tags_index = properties.find_index { |p| !p.is_a?(String)}
+    if tags_index
+      properties, tags = properties[0..tags_index-1], properties[tags_index..-1]
+    else
+      tags = []
+    end
+    properties = properties.map { |property| "property #{property}" }
+    context "With properties #{english_join(properties)}", *tags do
+      before do
+        properties.each do |property_str|
+          resource_class.class_eval(property_str, __FILE__, __LINE__)
+        end
+      end
+      instance_eval(&block)
+    end
+  end
+
+  # identity
+  context "Chef::Resource#identity_properties" do
+    with_property ":x" do
+      it "name is the default identity" do
+        expect(resource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ]
+        expect(Chef::Resource.properties[:name].identity?).to be_falsey
+        expect(resource.name).to eq 'blah'
+        expect(resource.identity).to eq 'blah'
+      end
+
+      it "identity_properties :x changes the identity" do
+        expect(resource_class.identity_properties :x).to eq [ resource_class.properties[:x] ]
+        expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ]
+        expect(Chef::Resource.properties[:name].identity?).to be_falsey
+        expect(resource_class.properties[:x].identity?).to be_truthy
+
+        expect(resource.x 'woo').to eq 'woo'
+        expect(resource.x).to eq 'woo'
+
+        expect(resource.name).to eq 'blah'
+        expect(resource.identity).to eq 'woo'
+      end
+
+      with_property ":y, identity: true" do
+        context "and identity_properties :x" do
+          before do
+            resource_class.class_eval do
+              identity_properties :x
+            end
+          end
+
+          it "only returns :x as identity" do
+            resource.x 'foo'
+            resource.y 'bar'
+            expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ]
+            expect(resource.identity).to eq 'foo'
+          end
+          it "does not flip y.desired_state off" do
+            resource.x 'foo'
+            resource.y 'bar'
+            expect(resource_class.state_properties).to eq [
+              resource_class.properties[:x],
+              resource_class.properties[:y]
+            ]
+            expect(resource.state_for_resource_reporter).to eq(x: 'foo', y: 'bar')
+          end
+        end
+      end
+
+      context "With a subclass" do
+        let(:subresource_class) do
+          new_resource_name = self.class.new_resource_name
+          Class.new(resource_class) do
+            resource_name new_resource_name
+          end
+        end
+        let(:subresource) do
+          subresource_class.new('sub')
+        end
+
+        it "name is the default identity on the subclass" do
+          expect(subresource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ]
+          expect(Chef::Resource.properties[:name].identity?).to be_falsey
+          expect(subresource.name).to eq 'sub'
+          expect(subresource.identity).to eq 'sub'
+        end
+
+        context "With identity_properties :x on the superclass" do
+          before do
+            resource_class.class_eval do
+              identity_properties :x
+            end
+          end
+
+          it "The subclass inherits :x as identity" do
+            expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x] ]
+            expect(Chef::Resource.properties[:name].identity?).to be_falsey
+            expect(subresource_class.properties[:x].identity?).to be_truthy
+
+            subresource.x 'foo'
+            expect(subresource.identity).to eq 'foo'
+          end
+
+          context "With property :y, identity: true on the subclass" do
+            before do
+              subresource_class.class_eval do
+                property :y, identity: true
+              end
+            end
+            it "The subclass's identity includes both x and y" do
+              expect(subresource_class.identity_properties).to eq [
+                subresource_class.properties[:x],
+                subresource_class.properties[:y]
+              ]
+              subresource.x 'foo'
+              subresource.y 'bar'
+              expect(subresource.identity).to eq(x: 'foo', y: 'bar')
+            end
+          end
+
+          with_property ":y, String" do
+            context "With identity_properties :y on the subclass" do
+              before do
+                subresource_class.class_eval do
+                  identity_properties :y
+                end
+              end
+              it "y is part of state" do
+                subresource.x 'foo'
+                subresource.y 'bar'
+                expect(subresource.state_for_resource_reporter).to eq(x: 'foo', y: 'bar')
+                expect(subresource_class.state_properties).to eq [
+                  subresource_class.properties[:x],
+                  subresource_class.properties[:y]
+                ]
+              end
+              it "y is the identity" do
+                expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:y] ]
+                subresource.x 'foo'
+                subresource.y 'bar'
+                expect(subresource.identity).to eq 'bar'
+              end
+              it "y still has validation" do
+                expect { subresource.y 12 }.to raise_error Chef::Exceptions::ValidationFailed
+              end
+            end
+          end
+        end
+      end
+    end
+
+    with_property ":string_only, String, identity: true", ":string_only2, String" do
+      it "identity_properties does not change validation" do
+        resource_class.identity_properties :string_only
+        expect { resource.string_only 12 }.to raise_error Chef::Exceptions::ValidationFailed
+        expect { resource.string_only2 12 }.to raise_error Chef::Exceptions::ValidationFailed
+      end
+    end
+
+    with_property ":x, desired_state: false" do
+      it "identity_properties does not change desired_state" do
+        resource_class.identity_properties :x
+        resource.x 'hi'
+        expect(resource.identity).to eq 'hi'
+        expect(resource_class.properties[:x].desired_state?).to be_falsey
+        expect(resource_class.state_properties).to eq []
+        expect(resource.state_for_resource_reporter).to eq({})
+      end
+    end
+
+    context "With custom property custom_property defined only as methods, using different variables for storage" do
+      before do
+        resource_class.class_eval do
+          def custom_property
+            @blarghle ? @blarghle*3 : nil
+          end
+          def custom_property=(x)
+            @blarghle = x*2
+          end
+        end
+      end
+
+      context "And identity_properties :custom_property" do
+        before do
+          resource_class.class_eval do
+            identity_properties :custom_property
+          end
+        end
+
+        it "identity_properties comes back as :custom_property" do
+          expect(resource_class.properties[:custom_property].identity?).to be_truthy
+          expect(resource_class.identity_properties).to eq [ resource_class.properties[:custom_property] ]
+        end
+        it "custom_property becomes part of desired_state" do
+          resource.custom_property = 1
+          expect(resource.state_for_resource_reporter).to eq(custom_property: 6)
+          expect(resource_class.properties[:custom_property].desired_state?).to be_truthy
+          expect(resource_class.state_properties).to eq [
+            resource_class.properties[:custom_property]
+          ]
+        end
+        it "identity_properties does not change custom_property's getter or setter" do
+          resource.custom_property = 1
+          expect(resource.custom_property).to eq 6
+        end
+        it "custom_property is returned as the identity" do
+          expect(resource.identity).to be_nil
+          resource.custom_property = 1
+          expect(resource.identity).to eq 6
+        end
+      end
+    end
+  end
+
+  context "Property#identity" do
+    with_property ":x, identity: true" do
+      it "name is only part of the identity if an identity attribute is defined" do
+        expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ]
+        resource.x 'woo'
+        expect(resource.identity).to eq 'woo'
+      end
+    end
+
+    with_property ":x, identity: true, default: 'xxx'",
+                  ":y, identity: true, default: 'yyy'",
+                  ":z, identity: true, default: 'zzz'" do
+      it "identity_property raises an error if multiple identity values are defined" do
+        expect { resource_class.identity_property }.to raise_error Chef::Exceptions::MultipleIdentityError
+      end
+      it "identity_attr raises an error if multiple identity values are defined" do
+        expect { resource_class.identity_attr }.to raise_error Chef::Exceptions::MultipleIdentityError
+      end
+      it "identity returns all identity values in a hash if multiple are defined" do
+        resource.x 'foo'
+        resource.y 'bar'
+        resource.z 'baz'
+        expect(resource.identity).to eq(x: 'foo', y: 'bar', z: 'baz')
+      end
+      it "identity returns all values whether any value is set or not" do
+        expect(resource.identity).to eq(x: 'xxx', y: 'yyy', z: 'zzz')
+      end
+      it "identity_properties wipes out any other identity attributes if multiple are defined" do
+        resource_class.identity_properties :y
+        resource.x 'foo'
+        resource.y 'bar'
+        resource.z 'baz'
+        expect(resource.identity).to eq 'bar'
+      end
+    end
+
+    with_property ":x, identity: true, name_property: true" do
+      it "identity when x is not defined returns the value of x" do
+        expect(resource.identity).to eq 'blah'
+      end
+      it "state when x is not defined returns the value of x" do
+        expect(resource.state_for_resource_reporter).to eq(x: 'blah')
+      end
+    end
+  end
+
+  # state_properties
+  context "Chef::Resource#state_properties" do
+    it "state_properties is empty by default" do
+      expect(Chef::Resource.state_properties).to eq []
+      expect(resource.state_for_resource_reporter).to eq({})
+    end
+
+    with_property ":x", ":y", ":z" do
+      it "x, y and z are state attributes" do
+        resource.x 1
+        resource.y 2
+        resource.z 3
+        expect(resource_class.state_properties).to eq [
+          resource_class.properties[:x],
+          resource_class.properties[:y],
+          resource_class.properties[:z]
+        ]
+        expect(resource.state_for_resource_reporter).to eq(x: 1, y: 2, z: 3)
+      end
+      it "values that are not set are not included in state" do
+        resource.x 1
+        expect(resource.state_for_resource_reporter).to eq(x: 1)
+      end
+      it "when no values are set, nothing is included in state" do
+      end
+    end
+
+    with_property ":x", ":y, desired_state: false", ":z, desired_state: true" do
+      it "x and z are state attributes, and y is not" do
+        resource.x 1
+        resource.y 2
+        resource.z 3
+        expect(resource_class.state_properties).to eq [
+          resource_class.properties[:x],
+          resource_class.properties[:z]
+        ]
+        expect(resource.state_for_resource_reporter).to eq(x: 1, z: 3)
+      end
+    end
+
+    with_property ":x, name_property: true" do
+      # it "Unset values with name_property are included in state" do
+      #   expect(resource.state_for_resource_reporter).to eq({ x: 'blah' })
+      # end
+      it "Set values with name_property are included in state" do
+        resource.x 1
+        expect(resource.state_for_resource_reporter).to eq(x: 1)
+      end
+    end
+
+    with_property ":x, default: 1" do
+      it "Unset values with defaults are not included in state" do
+        expect(resource.state_for_resource_reporter).to eq({})
+      end
+      it "Set values with defaults are included in state" do
+        resource.x 1
+        expect(resource.state_for_resource_reporter).to eq(x: 1)
+      end
+    end
+
+    context "With a class with a normal getter and setter" do
+      before do
+        resource_class.class_eval do
+          def x
+            @blah*3
+          end
+          def x=(value)
+            @blah = value*2
+          end
+        end
+      end
+      it "state_properties(:x) causes the value to be included in properties" do
+        resource_class.state_properties(:x)
+        resource.x = 1
+
+        expect(resource.x).to eq 6
+        expect(resource.state_for_resource_reporter).to eq(x: 6)
+      end
+    end
+
+    context "When state_properties happens before properties are declared" do
+      before do
+        resource_class.class_eval do
+          state_properties :x
+          property :x
+        end
+      end
+      it "the property works and is in state_properties" do
+        expect(resource_class.state_properties).to include(resource_class.properties[:x])
+        resource.x = 1
+        expect(resource.x).to eq 1
+        expect(resource.state_for_resource_reporter).to eq(x: 1)
+      end
+    end
+
+    with_property ":x, Integer, identity: true" do
+      it "state_properties(:x) leaves the property in desired_state" do
+        resource_class.state_properties(:x)
+        resource.x 10
+
+        expect(resource_class.properties[:x].desired_state?).to be_truthy
+        expect(resource_class.state_properties).to eq [
+          resource_class.properties[:x]
+        ]
+        expect(resource.state_for_resource_reporter).to eq(x: 10)
+      end
+      it "state_properties(:x) does not turn off validation" do
+        resource_class.state_properties(:x)
+        expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+      end
+      it "state_properties(:x) does not turn off identity" do
+        resource_class.state_properties(:x)
+        resource.x 10
+
+        expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ]
+        expect(resource_class.properties[:x].identity?).to be_truthy
+        expect(resource.identity).to eq 10
+      end
+    end
+
+    with_property ":x, Integer, identity: true, desired_state: false" do
+      before do
+        resource_class.class_eval do
+          def y
+            20
+          end
+        end
+      end
+
+      it "state_properties(:x) leaves x identical" do
+        old_value = resource_class.properties[:y]
+        resource_class.state_properties(:x)
+        resource.x 10
+
+        expect(resource_class.properties[:y].object_id).to eq old_value.object_id
+
+        expect(resource_class.properties[:x].desired_state?).to be_truthy
+        expect(resource_class.properties[:x].identity?).to be_truthy
+        expect(resource_class.identity_properties).to eq [
+          resource_class.properties[:x]
+        ]
+        expect(resource.identity).to eq(10)
+        expect(resource_class.state_properties).to eq [
+          resource_class.properties[:x]
+        ]
+        expect(resource.state_for_resource_reporter).to eq(x: 10)
+      end
+
+      it "state_properties(:y) adds y to desired state" do
+        old_value = resource_class.properties[:x]
+        resource_class.state_properties(:y)
+        resource.x 10
+
+        expect(resource_class.properties[:x].object_id).to eq old_value.object_id
+        expect(resource_class.properties[:x].desired_state?).to be_falsey
+        expect(resource_class.properties[:y].desired_state?).to be_truthy
+        expect(resource_class.state_properties).to eq [
+          resource_class.properties[:y]
+        ]
+        expect(resource.state_for_resource_reporter).to eq(y: 20)
+      end
+
+      context "With a subclassed resource" do
+        let(:subresource_class) do
+          new_resource_name = self.class.new_resource_name
+          Class.new(resource_class) do
+            resource_name new_resource_name
+          end
+        end
+        let(:subresource) do
+          subresource_class.new('blah')
+        end
+
+        it "state_properties(:x) adds x to desired state" do
+          old_value = resource_class.properties[:y]
+          subresource_class.state_properties(:x)
+          subresource.x 10
+
+          expect(subresource_class.properties[:y].object_id).to eq old_value.object_id
+
+          expect(subresource_class.properties[:x].desired_state?).to be_truthy
+          expect(subresource_class.properties[:x].identity?).to be_truthy
+          expect(subresource_class.identity_properties).to eq [
+            subresource_class.properties[:x]
+          ]
+          expect(subresource.identity).to eq(10)
+          expect(subresource_class.state_properties).to eq [
+            subresource_class.properties[:x]
+          ]
+          expect(subresource.state_for_resource_reporter).to eq(x: 10)
+        end
+
+        it "state_properties(:y) adds y to desired state" do
+          old_value = resource_class.properties[:x]
+          subresource_class.state_properties(:y)
+          subresource.x 10
+
+          expect(subresource_class.properties[:x].object_id).to eq old_value.object_id
+          expect(subresource_class.properties[:y].desired_state?).to be_truthy
+          expect(subresource_class.state_properties).to eq [
+            subresource_class.properties[:y]
+          ]
+          expect(subresource.state_for_resource_reporter).to eq(y: 20)
+
+          expect(subresource_class.properties[:x].identity?).to be_truthy
+          expect(subresource_class.identity_properties).to eq [
+            subresource_class.properties[:x]
+          ]
+          expect(subresource.identity).to eq(10)
+        end
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/property/validation_spec.rb b/spec/unit/property/validation_spec.rb
new file mode 100644
index 0000000..a54a38e
--- /dev/null
+++ b/spec/unit/property/validation_spec.rb
@@ -0,0 +1,665 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource.property validation" do
+  include IntegrationSupport
+
+  module Namer
+    @i = 0
+    def self.next_resource_name
+      "chef_resource_property_spec_#{@i += 1}"
+    end
+    def self.reset_index
+      @current_index = 0
+    end
+    def self.current_index
+      @current_index
+    end
+    def self.next_index
+      @current_index += 1
+    end
+  end
+
+  def lazy(&block)
+    Chef::DelayedEvaluator.new(&block)
+  end
+
+  before do
+    Namer.reset_index
+  end
+
+  def self.new_resource_name
+    Namer.next_resource_name
+  end
+
+  let(:resource_class) do
+    new_resource_name = self.class.new_resource_name
+    Class.new(Chef::Resource) do
+      resource_name new_resource_name
+      def blah
+        Namer.next_index
+      end
+      def self.blah
+        "class#{Namer.next_index}"
+      end
+    end
+  end
+
+  let(:resource) do
+    resource_class.new("blah")
+  end
+
+  def self.english_join(values)
+    return '<nothing>' if values.size == 0
+    return values[0].inspect if values.size == 1
+    "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}"
+  end
+
+  def self.with_property(*properties, &block)
+    tags_index = properties.find_index { |p| !p.is_a?(String)}
+    if tags_index
+      properties, tags = properties[0..tags_index-1], properties[tags_index..-1]
+    else
+      tags = []
+    end
+    properties = properties.map { |property| "property #{property}" }
+    context "With properties #{english_join(properties)}", *tags do
+      before do
+        properties.each do |property_str|
+          resource_class.class_eval(property_str, __FILE__, __LINE__)
+        end
+      end
+      instance_eval(&block)
+    end
+  end
+
+  def self.validation_test(validation, success_values, failure_values, getter_values=[], *tags)
+    with_property ":x, #{validation}", *tags do
+      it "gets nil when retrieving the initial (non-set) value" do
+        expect(resource.x).to be_nil
+      end
+      success_values.each do |v|
+        it "value #{v.inspect} is valid" do
+          resource.instance_eval { @x = 'default' }
+          expect(resource.x v).to eq v
+          expect(resource.x).to eq v
+        end
+      end
+      failure_values.each do |v|
+        it "value #{v.inspect} is invalid" do
+          expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed
+          resource.instance_eval { @x = 'default' }
+          expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed
+        end
+      end
+      getter_values.each do |v|
+        it "setting value to #{v.inspect} does not change the value" do
+          Chef::Config[:treat_deprecation_warnings_as_errors] = false
+          resource.instance_eval { @x = 'default' }
+          expect(resource.x v).to eq 'default'
+          expect(resource.x).to eq 'default'
+        end
+      end
+    end
+  end
+
+  context "basic get, set, and nil set" do
+    with_property ":x, kind_of: String" do
+      context "when the variable already has a value" do
+        before do
+          resource.instance_eval { @x = 'default' }
+        end
+        it "get succeeds" do
+          expect(resource.x).to eq 'default'
+        end
+        it "set to valid value succeeds" do
+          expect(resource.x 'str').to eq 'str'
+          expect(resource.x).to eq 'str'
+        end
+        it "set to invalid value raises ValidationFailed" do
+          expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+        end
+        it "set to nil emits a deprecation warning and does a get" do
+          expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+          Chef::Config[:treat_deprecation_warnings_as_errors] = false
+          resource.x 'str'
+          expect(resource.x nil).to eq 'str'
+          expect(resource.x).to eq 'str'
+        end
+      end
+      context "when the variable does not have an initial value" do
+        it "get succeeds" do
+          expect(resource.x).to be_nil
+        end
+        it "set to valid value succeeds" do
+          expect(resource.x 'str').to eq 'str'
+          expect(resource.x).to eq 'str'
+        end
+        it "set to invalid value raises ValidationFailed" do
+          expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+        end
+        it "set to nil emits no warning because the value would not change" do
+          expect(resource.x nil).to be_nil
+        end
+      end
+    end
+    with_property ":x, [ String, nil ]" do
+      context "when the variable already has a value" do
+        before do
+          resource.instance_eval { @x = 'default' }
+        end
+        it "get succeeds" do
+          expect(resource.x).to eq 'default'
+        end
+        it "set(nil) sets the value" do
+          expect(resource.x nil).to be_nil
+          expect(resource.x).to be_nil
+        end
+        it "set to valid value succeeds" do
+          expect(resource.x 'str').to eq 'str'
+          expect(resource.x).to eq 'str'
+        end
+        it "set to invalid value raises ValidationFailed" do
+          expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+        end
+      end
+      context "when the variable does not have an initial value" do
+        it "get succeeds" do
+          expect(resource.x).to be_nil
+        end
+        it "set(nil) sets the value" do
+          expect(resource.x nil).to be_nil
+          expect(resource.x).to be_nil
+        end
+        it "set to valid value succeeds" do
+          expect(resource.x 'str').to eq 'str'
+          expect(resource.x).to eq 'str'
+        end
+        it "set to invalid value raises ValidationFailed" do
+          expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+        end
+      end
+    end
+  end
+
+  # Bare types
+  context "bare types" do
+    validation_test 'String',
+      [ 'hi' ],
+      [ 10 ],
+      [ nil ]
+
+    validation_test ':a',
+      [ :a ],
+      [ :b ],
+      [ nil ]
+
+    validation_test ':a, is: :b',
+      [ :a, :b ],
+      [ :c ],
+      [ nil ]
+
+    validation_test ':a, is: [ :b, :c ]',
+      [ :a, :b, :c ],
+      [ :d ],
+      [ nil ]
+
+    validation_test '[ :a, :b ], is: :c',
+      [ :a, :b, :c ],
+      [ :d ],
+      [ nil ]
+
+    validation_test '[ :a, :b ], is: [ :c, :d ]',
+      [ :a, :b, :c, :d ],
+      [ :e ],
+      [ nil ]
+
+    validation_test 'nil',
+      [ nil ],
+      [ :a ]
+
+    validation_test '[ nil ]',
+      [ nil ],
+      [ :a ]
+
+    validation_test '[]',
+      [],
+      [ :a ],
+      [ nil ]
+  end
+
+  # is
+  context "is" do
+    # Class
+    validation_test 'is: String',
+      [ 'a', '' ],
+      [ :a, 1 ],
+      [ nil ]
+
+    # Value
+    validation_test 'is: :a',
+      [ :a ],
+      [ :b ],
+      [ nil ]
+
+    validation_test 'is: [ :a, :b ]',
+      [ :a, :b ],
+      [ [ :a, :b ] ],
+      [ nil ]
+
+    validation_test 'is: [ [ :a, :b ] ]',
+      [ [ :a, :b ] ],
+      [ :a, :b ],
+      [ nil ]
+
+    # Regex
+    validation_test 'is: /abc/',
+      [ 'abc', 'wowabcwow' ],
+      [ '', 'abac' ],
+      [ nil ]
+
+    # Property
+    validation_test 'is: Chef::Property.new(is: :a)',
+      [ :a ],
+      [ :b, nil ]
+
+    # RSpec Matcher
+    class Globalses
+      extend RSpec::Matchers
+    end
+
+    validation_test "is: Globalses.eq(10)",
+      [ 10 ],
+      [ 1 ],
+      [ nil ]
+
+    # Proc
+    validation_test 'is: proc { |x| x }',
+      [ true, 1 ],
+      [ false ],
+      [ nil ]
+
+    validation_test 'is: proc { |x| x > blah }',
+      [ 10 ],
+      [ -1 ]
+
+    validation_test 'is: nil',
+      [ nil ],
+      [ 'a' ]
+
+    validation_test 'is: [ String, nil ]',
+      [ 'a', nil ],
+      [ :b ]
+
+    validation_test 'is: []',
+      [],
+      [ :a ],
+      [ nil ]
+  end
+
+  # Combination
+  context "combination" do
+    validation_test 'kind_of: String, equal_to: "a"',
+      [ 'a' ],
+      [ 'b' ],
+      [ nil ]
+  end
+
+  # equal_to
+  context "equal_to" do
+    # Value
+    validation_test 'equal_to: :a',
+      [ :a ],
+      [ :b ],
+      [ nil ]
+
+    validation_test 'equal_to: [ :a, :b ]',
+      [ :a, :b ],
+      [ [ :a, :b ] ],
+      [ nil ]
+
+    validation_test 'equal_to: [ [ :a, :b ] ]',
+      [ [ :a, :b ] ],
+      [ :a, :b ],
+      [ nil ]
+
+    validation_test 'equal_to: nil',
+      [ ],
+      [ 'a' ],
+      [ nil ]
+
+    validation_test 'equal_to: [ "a", nil ]',
+      [ 'a' ],
+      [ 'b' ],
+      [ nil ]
+
+    validation_test 'equal_to: [ nil, "a" ]',
+      [ 'a' ],
+      [ 'b' ],
+      [ nil ]
+
+    validation_test 'equal_to: []',
+      [],
+      [ :a ],
+      [ nil ]
+  end
+
+  # kind_of
+  context "kind_of" do
+    validation_test 'kind_of: String',
+      [ 'a' ],
+      [ :b ],
+      [ nil ]
+
+    validation_test 'kind_of: [ String, Symbol ]',
+      [ 'a', :b ],
+      [ 1 ],
+      [ nil ]
+
+    validation_test 'kind_of: [ Symbol, String ]',
+      [ 'a', :b ],
+      [ 1 ],
+      [ nil ]
+
+    validation_test 'kind_of: NilClass',
+      [ ],
+      [ 'a' ],
+      [ nil ]
+
+    validation_test 'kind_of: [ NilClass, String ]',
+      [ 'a' ],
+      [ :a ],
+      [ nil ]
+
+    validation_test 'kind_of: []',
+      [],
+      [ :a ],
+      [ nil ]
+
+    validation_test 'kind_of: nil',
+      [],
+      [ :a ],
+      [ nil ]
+  end
+
+  # regex
+  context "regex" do
+    validation_test 'regex: /abc/',
+      [ 'xabcy' ],
+      [ 'gbh', 123 ],
+      [ nil ]
+
+    validation_test 'regex: [ /abc/, /z/ ]',
+      [ 'xabcy', 'aza' ],
+      [ 'gbh', 123 ],
+      [ nil ]
+
+    validation_test 'regex: [ /z/, /abc/ ]',
+      [ 'xabcy', 'aza' ],
+      [ 'gbh', 123 ],
+      [ nil ]
+
+    validation_test 'regex: [ [ /z/, /abc/ ], [ /n/ ] ]',
+      [ 'xabcy', 'aza', 'ana' ],
+      [ 'gbh', 123 ],
+      [ nil ]
+
+    validation_test 'regex: []',
+      [],
+      [ :a ],
+      [ nil ]
+
+    validation_test 'regex: nil',
+      [],
+      [ :a ],
+      [ nil ]
+  end
+
+  # callbacks
+  context "callbacks" do
+    validation_test 'callbacks: { "a" => proc { |x| x > 10 }, "b" => proc { |x| x%2 == 0 } }',
+      [ 12 ],
+      [ 11, 4 ]
+
+    validation_test 'callbacks: { "a" => proc { |x| x%2 == 0 }, "b" => proc { |x| x > 10 } }',
+      [ 12 ],
+      [ 11, 4 ]
+
+    validation_test 'callbacks: { "a" => proc { |x| x.nil? } }',
+      [ ],
+      [ 'a' ],
+      [ nil ]
+
+    validation_test 'callbacks: {}',
+      [ :a ],
+      [],
+      [ nil ]
+  end
+
+  # respond_to
+  context "respond_to" do
+    validation_test 'respond_to: :split',
+      [ 'hi' ],
+      [ 1 ],
+      [ nil ]
+
+    validation_test 'respond_to: "split"',
+      [ 'hi' ],
+      [ 1 ],
+      [ nil ]
+
+    validation_test 'respond_to: :to_s',
+      [ :a ],
+      [],
+      [ nil ]
+
+    validation_test 'respond_to: [ :split, :to_s ]',
+      [ 'hi' ],
+      [ 1 ],
+      [ nil ]
+
+    validation_test 'respond_to: %w(split to_s)',
+      [ 'hi' ],
+      [ 1 ],
+      [ nil ]
+
+    validation_test 'respond_to: [ :to_s, :split ]',
+      [ 'hi' ],
+      [ 1, ],
+      [ nil ]
+
+    validation_test 'respond_to: []',
+      [ :a ],
+      [],
+      [ nil ]
+
+    validation_test 'respond_to: nil',
+      [ :a ],
+      [],
+      [ nil ]
+  end
+
+  context "cannot_be" do
+    validation_test 'cannot_be: :empty',
+      [ 1, [1,2], { a: 10 } ],
+      [ [] ],
+      [ nil ]
+
+    validation_test 'cannot_be: "empty"',
+      [ 1, [1,2], { a: 10 } ],
+      [ [] ],
+      [ nil ]
+
+    validation_test 'cannot_be: [ :empty, :nil ]',
+      [ 1, [1,2], { a: 10 } ],
+      [ [] ],
+      [ nil ]
+
+    validation_test 'cannot_be: [ "empty", "nil" ]',
+      [ 1, [1,2], { a: 10 } ],
+      [ [] ],
+      [ nil ]
+
+    validation_test 'cannot_be: [ :nil, :empty ]',
+      [ 1, [1,2], { a: 10 } ],
+      [ [] ],
+      [ nil ]
+
+    validation_test 'cannot_be: [ :empty, :nil, :blahblah ]',
+      [ 1, [1,2], { a: 10 } ],
+      [ [] ],
+      [ nil ]
+
+    validation_test 'cannot_be: []',
+      [ :a ],
+      [],
+      [ nil ]
+
+    validation_test 'cannot_be: nil',
+      [ :a ],
+      [],
+      [ nil ]
+
+  end
+
+  context "required" do
+    with_property ':x, required: true' do
+      it "if x is not specified, retrieval fails" do
+        expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+      end
+      it "value 1 is valid" do
+        expect(resource.x 1).to eq 1
+        expect(resource.x).to eq 1
+      end
+      it "value nil emits a validation failed error because it must have a value" do
+        expect { resource.x nil }.to raise_error Chef::Exceptions::ValidationFailed
+      end
+      context "and value is set to something other than nil" do
+        before { resource.x 10 }
+        it "value nil emits a deprecation warning and does a get" do
+          expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+          Chef::Config[:treat_deprecation_warnings_as_errors] = false
+          resource.x 1
+          expect(resource.x nil).to eq 1
+          expect(resource.x).to eq 1
+        end
+      end
+    end
+
+    with_property ':x, [String, nil], required: true' do
+      it "if x is not specified, retrieval fails" do
+        expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+      end
+      it "value nil is valid" do
+        expect(resource.x nil).to be_nil
+        expect(resource.x).to be_nil
+      end
+      it "value '1' is valid" do
+        expect(resource.x '1').to eq '1'
+        expect(resource.x).to eq '1'
+      end
+      it "value 1 is invalid" do
+        expect { resource.x 1 }.to raise_error Chef::Exceptions::ValidationFailed
+      end
+    end
+
+    with_property ':x, name_property: true, required: true' do
+      it "if x is not specified, the name property is returned" do
+        expect(resource.x).to eq 'blah'
+      end
+      it "value 1 is valid" do
+        expect(resource.x 1).to eq 1
+        expect(resource.x).to eq 1
+      end
+      it "value nil emits a deprecation warning and does a get" do
+        expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+        Chef::Config[:treat_deprecation_warnings_as_errors] = false
+        resource.x 1
+        expect(resource.x nil).to eq 1
+        expect(resource.x).to eq 1
+      end
+    end
+
+    with_property ':x, default: 10, required: true' do
+      it "if x is not specified, the default is returned" do
+        expect(resource.x).to eq 10
+      end
+      it "value 1 is valid" do
+        expect(resource.x 1).to eq 1
+        expect(resource.x).to eq 1
+      end
+      it "value nil is invalid" do
+        expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+        Chef::Config[:treat_deprecation_warnings_as_errors] = false
+        resource.x 1
+        expect(resource.x nil).to eq 1
+        expect(resource.x).to eq 1
+      end
+    end
+  end
+
+  context "custom validators (def _pv_blarghle)" do
+    before do
+      Chef::Config[:treat_deprecation_warnings_as_errors] = false
+    end
+
+    with_property ':x, blarghle: 1' do
+      context "and a class that implements _pv_blarghle" do
+        before do
+          resource_class.class_eval do
+            def _pv_blarghle(opts, key, value)
+              if _pv_opts_lookup(opts, key) != value
+                raise Chef::Exceptions::ValidationFailed, "ouch"
+              end
+            end
+          end
+        end
+
+        it "value 1 is valid" do
+          expect(resource.x 1).to eq 1
+          expect(resource.x).to eq 1
+        end
+
+        it "value '1' is invalid" do
+          Chef::Config[:treat_deprecation_warnings_as_errors] = false
+          expect { resource.x '1' }.to raise_error Chef::Exceptions::ValidationFailed
+        end
+
+        it "value nil does a get" do
+          Chef::Config[:treat_deprecation_warnings_as_errors] = false
+          resource.x 1
+          resource.x nil
+          expect(resource.x).to eq 1
+        end
+      end
+    end
+
+    with_property ':x, blarghle: 1' do
+      context "and a class that implements _pv_blarghle" do
+        before do
+          resource_class.class_eval do
+            def _pv_blarghle(opts, key, value)
+              if _pv_opts_lookup(opts, key) != value
+                raise Chef::Exceptions::ValidationFailed, "ouch"
+              end
+            end
+          end
+        end
+
+        it "value 1 is valid" do
+          expect(resource.x 1).to eq 1
+          expect(resource.x).to eq 1
+        end
+
+        it "value '1' is invalid" do
+          expect { resource.x '1' }.to raise_error Chef::Exceptions::ValidationFailed
+        end
+
+        it "value nil does a get" do
+          resource.x 1
+          resource.x nil
+          expect(resource.x).to eq 1
+        end
+      end
+    end
+  end
+end
diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb
new file mode 100644
index 0000000..095dcc8
--- /dev/null
+++ b/spec/unit/property_spec.rb
@@ -0,0 +1,1150 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource.property" do
+  include IntegrationSupport
+
+  module Namer
+    @i = 0
+    def self.next_resource_name
+      "chef_resource_property_spec_#{@i += 1}"
+    end
+    def self.reset_index
+      @current_index = 0
+    end
+    def self.current_index
+      @current_index
+    end
+    def self.next_index
+      @current_index += 1
+    end
+  end
+
+  def lazy(&block)
+    Chef::DelayedEvaluator.new(&block)
+  end
+
+  before do
+    Namer.reset_index
+  end
+
+  def self.new_resource_name
+    Namer.next_resource_name
+  end
+
+  let(:resource_class) do
+    new_resource_name = self.class.new_resource_name
+    Class.new(Chef::Resource) do
+      resource_name new_resource_name
+      def next_index
+        Namer.next_index
+      end
+    end
+  end
+
+  let(:resource) do
+    resource_class.new("blah")
+  end
+
+  def self.english_join(values)
+    return '<nothing>' if values.size == 0
+    return values[0].inspect if values.size == 1
+    "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}"
+  end
+
+  def self.with_property(*properties, &block)
+    tags_index = properties.find_index { |p| !p.is_a?(String)}
+    if tags_index
+      properties, tags = properties[0..tags_index-1], properties[tags_index..-1]
+    else
+      tags = []
+    end
+    if properties.size == 1
+      description = "With property #{properties.first}"
+    else
+      description = "With properties #{english_join(properties.map { |property| "#{property.inspect}" })}"
+    end
+    context description, *tags do
+      before do
+        properties.each do |property_str|
+          resource_class.class_eval("property #{property_str}", __FILE__, __LINE__)
+        end
+      end
+      instance_eval(&block)
+    end
+  end
+
+  # Basic properties
+  with_property ':bare_property' do
+    it "can be set" do
+      expect(resource.bare_property 10).to eq 10
+      expect(resource.bare_property).to eq 10
+    end
+    it "emits a deprecation warning and does a get, if set to nil" do
+      expect(resource.bare_property 10).to eq 10
+      expect { resource.bare_property nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+      Chef::Config[:treat_deprecation_warnings_as_errors] = false
+      expect(resource.bare_property nil).to eq 10
+      expect(resource.bare_property).to eq 10
+    end
+    it "can be updated" do
+      expect(resource.bare_property 10).to eq 10
+      expect(resource.bare_property 20).to eq 20
+      expect(resource.bare_property).to eq 20
+    end
+    it "can be set with =" do
+      expect(resource.bare_property 10).to eq 10
+      expect(resource.bare_property).to eq 10
+    end
+    it "can be set to nil with =" do
+      expect(resource.bare_property 10).to eq 10
+      expect(resource.bare_property = nil).to be_nil
+      expect(resource.bare_property).to be_nil
+    end
+    it "can be updated with =" do
+      expect(resource.bare_property 10).to eq 10
+      expect(resource.bare_property = 20).to eq 20
+      expect(resource.bare_property).to eq 20
+    end
+  end
+
+  with_property ":x, name_property: true" do
+    context "and subclass" do
+      let(:subresource_class) do
+        new_resource_name = self.class.new_resource_name
+        Class.new(resource_class) do
+          resource_name new_resource_name
+        end
+      end
+      let(:subresource) do
+        subresource_class.new('blah')
+      end
+
+      context "with property :x on the subclass" do
+        before do
+          subresource_class.class_eval do
+            property :x
+          end
+        end
+
+        it "x is still name_property" do
+          expect(subresource.x).to eq 'blah'
+        end
+      end
+
+      context "with property :x, name_attribute: false on the subclass" do
+        before do
+          subresource_class.class_eval do
+            property :x, name_attribute: false
+          end
+        end
+
+        it "x is no longer name_property" do
+          expect(subresource.x).to be_nil
+        end
+      end
+
+      context "with property :x, default: 10 on the subclass" do
+        before do
+          subresource_class.class_eval do
+            property :x, default: 10
+          end
+        end
+
+        it "x is no longer name_property" do
+          expect(subresource.x).to eq(10)
+        end
+      end
+    end
+  end
+
+  with_property ":x, Integer" do
+    context "and subclass" do
+      let(:subresource_class) do
+        new_resource_name = self.class.new_resource_name
+        Class.new(resource_class) do
+          resource_name new_resource_name
+        end
+      end
+      let(:subresource) do
+        subresource_class.new('blah')
+      end
+
+      it "x is inherited" do
+        expect(subresource.x 10).to eq 10
+        expect(subresource.x).to eq 10
+        expect(subresource.x = 20).to eq 20
+        expect(subresource.x).to eq 20
+        expect(subresource_class.properties[:x]).not_to be_nil
+      end
+
+      it "x's validation is inherited" do
+        expect { subresource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+      end
+
+      context "with property :y on the subclass" do
+        before do
+          subresource_class.class_eval do
+            property :y
+          end
+        end
+
+        it "x is still there" do
+          expect(subresource.x 10).to eq 10
+          expect(subresource.x).to eq 10
+          expect(subresource.x = 20).to eq 20
+          expect(subresource.x).to eq 20
+          expect(subresource_class.properties[:x]).not_to be_nil
+        end
+        it "y is there" do
+          expect(subresource.y 10).to eq 10
+          expect(subresource.y).to eq 10
+          expect(subresource.y = 20).to eq 20
+          expect(subresource.y).to eq 20
+          expect(subresource_class.properties[:y]).not_to be_nil
+        end
+        it "y is not on the superclass" do
+          expect { resource_class.y 10 }.to raise_error
+          expect(resource_class.properties[:y]).to be_nil
+        end
+      end
+
+      context "with property :x on the subclass" do
+        before do
+          subresource_class.class_eval do
+            property :x
+          end
+        end
+
+        it "x is still there" do
+          expect(subresource.x 10).to eq 10
+          expect(subresource.x).to eq 10
+          expect(subresource.x = 20).to eq 20
+          expect(subresource.x).to eq 20
+          expect(subresource_class.properties[:x]).not_to be_nil
+          expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x]
+        end
+
+        it "x's validation is inherited" do
+          expect { subresource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+        end
+      end
+
+      context "with property :x, default: 80 on the subclass" do
+        before do
+          subresource_class.class_eval do
+            property :x, default: 80
+          end
+        end
+
+        it "x is still there" do
+          expect(subresource.x 10).to eq 10
+          expect(subresource.x).to eq 10
+          expect(subresource.x = 20).to eq 20
+          expect(subresource.x).to eq 20
+          expect(subresource_class.properties[:x]).not_to be_nil
+          expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x]
+        end
+
+        it "x defaults to 80" do
+          expect(subresource.x).to eq 80
+        end
+
+        it "x's validation is inherited" do
+          expect { subresource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+        end
+      end
+
+      context "with property :x, String on the subclass" do
+        before do
+          subresource_class.class_eval do
+            property :x, String
+          end
+        end
+
+        it "x is still there" do
+          expect(subresource.x "10").to eq "10"
+          expect(subresource.x).to eq "10"
+          expect(subresource.x = "20").to eq "20"
+          expect(subresource.x).to eq "20"
+          expect(subresource_class.properties[:x]).not_to be_nil
+          expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x]
+        end
+
+        it "x's validation is overwritten" do
+          expect { subresource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+          expect(subresource.x 'ohno').to eq 'ohno'
+          expect(subresource.x).to eq 'ohno'
+        end
+
+        it "the superclass's validation for x is still there" do
+          expect { resource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+          expect(resource.x 10).to eq 10
+          expect(resource.x).to eq 10
+        end
+      end
+    end
+  end
+
+  context "Chef::Resource::Property#reset_property" do
+    it "when a resource is newly created, reset_property(:name) sets property to nil" do
+      expect(resource.property_is_set?(:name)).to be_truthy
+      resource.reset_property(:name)
+      expect(resource.property_is_set?(:name)).to be_falsey
+      expect(resource.name).to be_nil
+    end
+
+    it "when referencing an undefined property, reset_property(:x) raises an error" do
+      expect { resource.reset_property(:x) }.to raise_error(ArgumentError)
+    end
+
+    with_property ':x' do
+      it "when the resource is newly created, reset_property(:x) does nothing" do
+        expect(resource.property_is_set?(:x)).to be_falsey
+        resource.reset_property(:x)
+        expect(resource.property_is_set?(:x)).to be_falsey
+        expect(resource.x).to be_nil
+      end
+      it "when x is set, reset_property resets it" do
+        resource.x 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+        resource.reset_property(:x)
+        expect(resource.property_is_set?(:x)).to be_falsey
+        expect(resource.x).to be_nil
+      end
+    end
+
+    with_property ':x, Integer' do
+      it "when the resource is newly created, reset_property(:x) does nothing" do
+        expect(resource.property_is_set?(:x)).to be_falsey
+        resource.reset_property(:x)
+        expect(resource.property_is_set?(:x)).to be_falsey
+        expect(resource.x).to be_nil
+      end
+      it "when x is set, reset_property resets it even though `nil` is technically invalid" do
+        resource.x 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+        resource.reset_property(:x)
+        expect(resource.property_is_set?(:x)).to be_falsey
+        expect(resource.x).to be_nil
+      end
+    end
+
+    with_property ':x, default: 10' do
+      it "when the resource is newly created, reset_property(:x) does nothing" do
+        expect(resource.property_is_set?(:x)).to be_falsey
+        resource.reset_property(:x)
+        expect(resource.property_is_set?(:x)).to be_falsey
+        expect(resource.x).to eq 10
+      end
+      it "when x is set, reset_property resets it and it returns the default" do
+        resource.x 20
+        resource.reset_property(:x)
+        expect(resource.property_is_set?(:x)).to be_falsey
+        expect(resource.x).to eq 10
+      end
+    end
+
+    with_property ':x, default: lazy { 10 }' do
+      it "when the resource is newly created, reset_property(:x) does nothing" do
+        expect(resource.property_is_set?(:x)).to be_falsey
+        resource.reset_property(:x)
+        expect(resource.property_is_set?(:x)).to be_falsey
+        expect(resource.x).to eq 10
+      end
+      it "when x is set, reset_property resets it and it returns the default" do
+        resource.x 20
+        resource.reset_property(:x)
+        expect(resource.property_is_set?(:x)).to be_falsey
+        expect(resource.x).to eq 10
+      end
+    end
+  end
+
+  context "Chef::Resource::Property#property_is_set?" do
+    it "when a resource is newly created, property_is_set?(:name) is true" do
+      expect(resource.property_is_set?(:name)).to be_truthy
+    end
+
+    it "when referencing an undefined property, property_is_set?(:x) raises an error" do
+      expect { resource.property_is_set?(:x) }.to raise_error(ArgumentError)
+    end
+
+    with_property ':x' do
+      it "when the resource is newly created, property_is_set?(:x) is false" do
+        expect(resource.property_is_set?(:x)).to be_falsey
+      end
+      it "when x is set, property_is_set?(:x) is true" do
+        resource.x 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is set with =, property_is_set?(:x) is true" do
+        resource.x = 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is set to a lazy value, property_is_set?(:x) is true" do
+        resource.x lazy { 10 }
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is retrieved, property_is_set?(:x) is false" do
+        resource.x
+        expect(resource.property_is_set?(:x)).to be_falsey
+      end
+    end
+
+    with_property ':x, default: 10' do
+      it "when the resource is newly created, property_is_set?(:x) is false" do
+        expect(resource.property_is_set?(:x)).to be_falsey
+      end
+      it "when x is set, property_is_set?(:x) is true" do
+        resource.x 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is set with =, property_is_set?(:x) is true" do
+        resource.x = 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is set to a lazy value, property_is_set?(:x) is true" do
+        resource.x lazy { 10 }
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is retrieved, property_is_set?(:x) is false" do
+        resource.x
+        expect(resource.property_is_set?(:x)).to be_falsey
+      end
+    end
+
+    with_property ':x, default: nil' do
+      it "when the resource is newly created, property_is_set?(:x) is false" do
+        expect(resource.property_is_set?(:x)).to be_falsey
+      end
+      it "when x is set, property_is_set?(:x) is true" do
+        resource.x 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is set with =, property_is_set?(:x) is true" do
+        resource.x = 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is set to a lazy value, property_is_set?(:x) is true" do
+        resource.x lazy { 10 }
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is retrieved, property_is_set?(:x) is false" do
+        resource.x
+        expect(resource.property_is_set?(:x)).to be_falsey
+      end
+    end
+
+    with_property ':x, default: lazy { 10 }' do
+      it "when the resource is newly created, property_is_set?(:x) is false" do
+        expect(resource.property_is_set?(:x)).to be_falsey
+      end
+      it "when x is set, property_is_set?(:x) is true" do
+        resource.x 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is set with =, property_is_set?(:x) is true" do
+        resource.x = 10
+        expect(resource.property_is_set?(:x)).to be_truthy
+      end
+      it "when x is retrieved, property_is_set?(:x) is false" do
+        resource.x
+        expect(resource.property_is_set?(:x)).to be_falsey
+      end
+    end
+  end
+
+  context "Chef::Resource::Property#default" do
+    with_property ':x, default: 10' do
+      it "when x is set, it returns its value" do
+        expect(resource.x 20).to eq 20
+        expect(resource.property_is_set?(:x)).to be_truthy
+        expect(resource.x).to eq 20
+      end
+      it "when x is not set, it returns 10" do
+        expect(resource.x).to eq 10
+      end
+      it "when x is not set, it is not included in state" do
+        expect(resource.state_for_resource_reporter).to eq({})
+      end
+      it "when x is set to nil, it returns nil" do
+        resource.instance_eval { @x = nil }
+        expect(resource.x).to be_nil
+      end
+
+      context "With a subclass" do
+        let(:subresource_class) do
+          new_resource_name = self.class.new_resource_name
+          Class.new(resource_class) do
+            resource_name new_resource_name
+          end
+        end
+        let(:subresource) { subresource_class.new('blah') }
+        it "The default is inherited" do
+          expect(subresource.x).to eq 10
+        end
+      end
+    end
+
+    with_property ':x, default: 10, identity: true' do
+      it "when x is not set, it is included in identity" do
+        expect(resource.identity).to eq(10)
+      end
+    end
+
+    with_property ':x, default: 1, identity: true', ':y, default: 2, identity: true' do
+      it "when x is not set, it is still included in identity" do
+        resource.y 20
+        expect(resource.identity).to eq(x: 1, y: 20)
+      end
+    end
+
+    with_property ':x, default: nil' do
+      it "when x is not set, it returns nil" do
+        expect(resource.x).to be_nil
+      end
+    end
+
+    with_property ':x' do
+      it "when x is not set, it returns nil" do
+        expect(resource.x).to be_nil
+      end
+    end
+
+    context "hash default" do
+      context "(deprecations allowed)" do
+        before { Chef::Config[:treat_deprecation_warnings_as_errors] = false }
+
+        with_property ':x, default: {}' do
+          it "when x is not set, it returns {}" do
+            expect(resource.x).to eq({})
+          end
+          it "The same exact value is returned multiple times in a row" do
+            value = resource.x
+            expect(value).to eq({})
+            expect(resource.x.object_id).to eq(value.object_id)
+          end
+          it "Multiple instances of x receive the exact same value" do
+            expect(resource.x.object_id).to eq(resource_class.new('blah2').x.object_id)
+          end
+        end
+      end
+
+      with_property ':x, default: lazy { {} }' do
+        it "when x is not set, it returns {}" do
+          expect(resource.x).to eq({})
+        end
+        # it "The value is different each time it is called" do
+        #   value = resource.x
+        #   expect(value).to eq({})
+        #   expect(resource.x.object_id).not_to eq(value.object_id)
+        # end
+        it "Multiple instances of x receive different values" do
+          expect(resource.x.object_id).not_to eq(resource_class.new('blah2').x.object_id)
+        end
+      end
+    end
+
+    context "with a class with 'blah' as both class and instance methods" do
+      before do
+        resource_class.class_eval do
+          def self.blah
+            'class'
+          end
+          def blah
+            "#{name}#{next_index}"
+          end
+        end
+      end
+
+      with_property ':x, default: lazy { blah }' do
+        it "x is run in context of the instance" do
+          expect(resource.x).to eq "blah1"
+        end
+        it "x is run in the context of each instance it is run in" do
+          expect(resource.x).to eq "blah1"
+          expect(resource_class.new('another').x).to eq "another2"
+          # expect(resource.x).to eq "blah3"
+        end
+      end
+
+      with_property ':x, default: lazy { |x| "#{blah}#{x.blah}" }' do
+        it "x is run in context of the class (where it was defined) and passed the instance" do
+          expect(resource.x).to eq "classblah1"
+        end
+        it "x is passed the value of each instance it is run in" do
+          expect(resource.x).to eq "classblah1"
+          expect(resource_class.new('another').x).to eq "classanother2"
+          # expect(resource.x).to eq "classblah3"
+        end
+      end
+    end
+
+    context "validation of defaults" do
+      with_property ':x, String, default: 10' do
+        it "when the resource is created, no error is raised" do
+          resource
+        end
+        it "when x is set, no error is raised" do
+          expect(resource.x 'hi').to eq 'hi'
+          expect(resource.x).to eq 'hi'
+        end
+        it "when x is retrieved, no validation error is raised" do
+          expect(resource.x).to eq 10
+        end
+        # it "when x is retrieved, a validation error is raised" do
+        #   expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+        # end
+      end
+
+      with_property ":x, String, default: lazy { Namer.next_index }" do
+        it "when the resource is created, no error is raised" do
+          resource
+        end
+        it "when x is set, no error is raised" do
+          expect(resource.x 'hi').to eq 'hi'
+          expect(resource.x).to eq 'hi'
+        end
+        it "when x is retrieved, no validation error is raised" do
+          expect(resource.x).to eq 1
+          expect(Namer.current_index).to eq 1
+        end
+        # it "when x is retrieved, a validation error is raised" do
+        #   expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+        #   expect(Namer.current_index).to eq 1
+        # end
+      end
+
+      with_property ":x, default: lazy { Namer.next_index.to_s }, is: proc { |v| Namer.next_index; true }" do
+        it "validation is not run at all on the default value" do
+          expect(resource.x).to eq '1'
+          expect(Namer.current_index).to eq 1
+        end
+        # it "validation is run each time" do
+        #   expect(resource.x).to eq '1'
+        #   expect(Namer.current_index).to eq 2
+        #   expect(resource.x).to eq '1'
+        #   expect(Namer.current_index).to eq 2
+        # end
+      end
+
+      with_property ":x, default: lazy { Namer.next_index.to_s.freeze }, is: proc { |v| Namer.next_index; true }" do
+        it "validation is not run at all on the default value" do
+          expect(resource.x).to eq '1'
+          expect(Namer.current_index).to eq 1
+        end
+        # it "validation is only run the first time" do
+        #   expect(resource.x).to eq '1'
+        #   expect(Namer.current_index).to eq 2
+        #   expect(resource.x).to eq '1'
+        #   expect(Namer.current_index).to eq 2
+        # end
+      end
+    end
+
+    context "coercion of defaults" do
+      # Frozen default, non-frozen coerce
+      with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do
+        it "when the resource is created, the proc is not yet run" do
+          resource
+          expect(Namer.current_index).to eq 0
+        end
+        it "when x is set, coercion is run" do
+          expect(resource.x 'hi').to eq 'hi1'
+          expect(resource.x).to eq 'hi1'
+          expect(Namer.current_index).to eq 1
+        end
+        it "when x is retrieved, coercion is run exactly once" do
+          expect(resource.x).to eq '101'
+          expect(resource.x).to eq '101'
+          expect(Namer.current_index).to eq 1
+        end
+      end
+
+      # Frozen default, frozen coerce
+      with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: 10' do
+        it "when the resource is created, the proc is not yet run" do
+          resource
+          expect(Namer.current_index).to eq 0
+        end
+        it "when x is set, coercion is run" do
+          expect(resource.x 'hi').to eq 'hi1'
+          expect(resource.x).to eq 'hi1'
+          expect(Namer.current_index).to eq 1
+        end
+        it "when x is retrieved, coercion is run each time" do
+          expect(resource.x).to eq '101'
+          expect(resource.x).to eq '102'
+          expect(Namer.current_index).to eq 2
+        end
+      end
+
+      # Frozen lazy default, non-frozen coerce
+      with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+        it "when the resource is created, the proc is not yet run" do
+          resource
+          expect(Namer.current_index).to eq 0
+        end
+        it "when x is set, coercion is run" do
+          expect(resource.x 'hi').to eq 'hi1'
+          expect(resource.x).to eq 'hi1'
+          expect(Namer.current_index).to eq 1
+        end
+        it "when x is retrieved, coercion is run exactly once" do
+          expect(resource.x).to eq '101'
+          expect(resource.x).to eq '101'
+          expect(Namer.current_index).to eq 1
+        end
+      end
+
+      # Non-frozen lazy default, frozen coerce
+      with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: lazy { "10" }' do
+        it "when the resource is created, the proc is not yet run" do
+          resource
+          expect(Namer.current_index).to eq 0
+        end
+        it "when x is set, coercion is run" do
+          expect(resource.x 'hi').to eq 'hi1'
+          expect(resource.x).to eq 'hi1'
+          expect(Namer.current_index).to eq 1
+        end
+        it "when x is retrieved, coercion is run each time" do
+          expect(resource.x).to eq '101'
+          expect(resource.x).to eq '102'
+          expect(Namer.current_index).to eq 2
+        end
+      end
+
+      with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+        it "coercion is only run the first time x is retrieved, and validation is not run" do
+          expect(Namer.current_index).to eq 0
+          expect(resource.x).to eq '101'
+          expect(Namer.current_index).to eq 1
+          expect(resource.x).to eq '101'
+          expect(Namer.current_index).to eq 1
+        end
+      end
+
+      context "validation and coercion of defaults" do
+        with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do
+          it "when x is retrieved, it is coerced before validating and passes" do
+            expect(resource.x).to eq '101'
+          end
+        end
+        with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do
+          it "when x is retrieved, it is coerced and not validated" do
+            expect(resource.x).to eq '101'
+          end
+          # it "when x is retrieved, it is coerced before validating and fails" do
+          #   expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+          # end
+        end
+        with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+          it "when x is retrieved, it is coerced before validating and passes" do
+            expect(resource.x).to eq '101'
+          end
+        end
+        with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+          it "when x is retrieved, it is coerced and not validated" do
+            expect(resource.x).to eq '101'
+          end
+          # it "when x is retrieved, it is coerced before validating and fails" do
+          #   expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+          # end
+        end
+        with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+          it "coercion is only run the first time x is retrieved, and validation is not run" do
+            expect(Namer.current_index).to eq 0
+            expect(resource.x).to eq '101'
+            expect(Namer.current_index).to eq 1
+            expect(resource.x).to eq '101'
+            expect(Namer.current_index).to eq 1
+          end
+        end
+      end
+    end
+  end
+
+  context "Chef::Resource#lazy" do
+    with_property ':x' do
+      it "setting x to a lazy value does not run it immediately" do
+        resource.x lazy { Namer.next_index }
+        expect(Namer.current_index).to eq 0
+      end
+      it "you can set x to a lazy value in the instance" do
+        resource.instance_eval do
+          x lazy { Namer.next_index }
+        end
+        expect(resource.x).to eq 1
+        expect(Namer.current_index).to eq 1
+      end
+      it "retrieving a lazy value pops it open" do
+        resource.x lazy { Namer.next_index }
+        expect(resource.x).to eq 1
+        expect(Namer.current_index).to eq 1
+      end
+      it "retrieving a lazy value twice evaluates it twice" do
+        resource.x lazy { Namer.next_index }
+        expect(resource.x).to eq 1
+        expect(resource.x).to eq 2
+        expect(Namer.current_index).to eq 2
+      end
+      it "setting the same lazy value on two different instances runs it on each instancee" do
+        resource2 = resource_class.new("blah2")
+        l = lazy { Namer.next_index }
+        resource.x l
+        resource2.x l
+        expect(resource2.x).to eq 1
+        expect(resource.x).to eq 2
+        expect(resource2.x).to eq 3
+      end
+
+      context "when the class has a class and instance method named blah" do
+        before do
+          resource_class.class_eval do
+            def self.blah
+              "class"
+            end
+            def blah
+              "#{name}#{Namer.next_index}"
+            end
+          end
+        end
+        def blah
+          "example"
+        end
+        # it "retrieving lazy { blah } gets the instance variable" do
+        #   resource.x lazy { blah }
+        #   expect(resource.x).to eq "blah1"
+        # end
+        # it "retrieving lazy { blah } from two different instances gets two different instance variables" do
+        #   resource2 = resource_class.new("another")
+        #   l = lazy { blah }
+        #   resource2.x l
+        #   resource.x l
+        #   expect(resource2.x).to eq "another1"
+        #   expect(resource.x).to eq "blah2"
+        #   expect(resource2.x).to eq "another3"
+        # end
+        it 'retrieving lazy { |x| "#{blah}#{x.blah}" } gets the example and instance variables' do
+          resource.x lazy { |x| "#{blah}#{x.blah}" }
+          expect(resource.x).to eq "exampleblah1"
+        end
+        it 'retrieving lazy { |x| "#{blah}#{x.blah}" } from two different instances gets two different instance variables' do
+          resource2 = resource_class.new("another")
+          l = lazy { |x| "#{blah}#{x.blah}" }
+          resource2.x l
+          resource.x l
+          expect(resource2.x).to eq "exampleanother1"
+          expect(resource.x).to eq "exampleblah2"
+          expect(resource2.x).to eq "exampleanother3"
+        end
+      end
+    end
+
+    with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+      it "lazy values are not coerced on set" do
+        resource.x lazy { Namer.next_index }
+        expect(Namer.current_index).to eq 0
+      end
+      it "lazy values are coerced on get" do
+        resource.x lazy { Namer.next_index }
+        expect(resource.x).to eq "12"
+        expect(Namer.current_index).to eq 2
+      end
+      it "lazy values are coerced on each access" do
+        resource.x lazy { Namer.next_index }
+        expect(resource.x).to eq "12"
+        expect(Namer.current_index).to eq 2
+        expect(resource.x).to eq "34"
+        expect(Namer.current_index).to eq 4
+      end
+    end
+
+    with_property ':x, String' do
+      it "lazy values are not validated on set" do
+        resource.x lazy { Namer.next_index }
+        expect(Namer.current_index).to eq 0
+      end
+      it "lazy values are validated on get" do
+        resource.x lazy { Namer.next_index }
+        expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+        expect(Namer.current_index).to eq 1
+      end
+    end
+
+    with_property ':x, is: proc { |v| Namer.next_index; true }' do
+      it "lazy values are validated on each access" do
+        resource.x lazy { Namer.next_index }
+        expect(resource.x).to eq 1
+        expect(Namer.current_index).to eq 2
+        expect(resource.x).to eq 3
+        expect(Namer.current_index).to eq 4
+      end
+    end
+
+    with_property ':x, Integer, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+      it "lazy values are not validated or coerced on set" do
+        resource.x lazy { Namer.next_index }
+        expect(Namer.current_index).to eq 0
+      end
+      it "lazy values are coerced before being validated, which fails" do
+        resource.x lazy { Namer.next_index }
+        expect(Namer.current_index).to eq 0
+        expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+        expect(Namer.current_index).to eq 2
+      end
+    end
+
+    with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }, is: proc { |v| Namer.next_index; true }' do
+      it "lazy values are coerced and validated exactly once" do
+        resource.x lazy { Namer.next_index }
+        expect(resource.x).to eq "12"
+        expect(Namer.current_index).to eq 3
+        expect(resource.x).to eq "45"
+        expect(Namer.current_index).to eq 6
+      end
+    end
+
+    with_property ':x, String, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+      it "lazy values are coerced before being validated, which succeeds" do
+        resource.x lazy { Namer.next_index }
+        expect(resource.x).to eq "12"
+        expect(Namer.current_index).to eq 2
+      end
+    end
+  end
+
+  context "Chef::Resource::Property#coerce" do
+    with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+      it "coercion runs on set" do
+        expect(resource.x 10).to eq "101"
+        expect(Namer.current_index).to eq 1
+      end
+      it "does not emit a deprecation warning if set to nil" do
+        expect(resource.x nil).to eq "1"
+      end
+      it "coercion sets the value (and coercion does not run on get)" do
+        expect(resource.x 10).to eq "101"
+        expect(resource.x).to eq "101"
+        expect(Namer.current_index).to eq 1
+      end
+      it "coercion runs each time set happens" do
+        expect(resource.x 10).to eq "101"
+        expect(Namer.current_index).to eq 1
+        expect(resource.x 10).to eq "102"
+        expect(Namer.current_index).to eq 2
+      end
+    end
+    with_property ':x, coerce: proc { |x| x }' do
+      it "does not emit a deprecation warning if set to nil" do
+        expect(resource.x nil).to be_nil
+      end
+    end
+    with_property ':x, coerce: proc { |x| Namer.next_index; raise "hi" if x == 10; x }, is: proc { |x| Namer.next_index; x != 10 }' do
+      it "failed coercion fails to set the value" do
+        resource.x 20
+        expect(resource.x).to eq 20
+        expect(Namer.current_index).to eq 2
+        expect { resource.x 10 }.to raise_error 'hi'
+        expect(resource.x).to eq 20
+        expect(Namer.current_index).to eq 3
+      end
+      it "validation does not run if coercion fails" do
+        expect { resource.x 10 }.to raise_error 'hi'
+        expect(Namer.current_index).to eq 1
+      end
+    end
+  end
+
+  context "Chef::Resource::Property validation" do
+    with_property ':x, is: proc { |v| Namer.next_index; v.is_a?(Integer) }' do
+      it "validation runs on set" do
+        expect(resource.x 10).to eq 10
+        expect(Namer.current_index).to eq 1
+      end
+      it "validation sets the value (and validation does not run on get)" do
+        expect(resource.x 10).to eq 10
+        expect(resource.x).to eq 10
+        expect(Namer.current_index).to eq 1
+      end
+      it "validation runs each time set happens" do
+        expect(resource.x 10).to eq 10
+        expect(Namer.current_index).to eq 1
+        expect(resource.x 10).to eq 10
+        expect(Namer.current_index).to eq 2
+      end
+      it "failed validation fails to set the value" do
+        expect(resource.x 10).to eq 10
+        expect(Namer.current_index).to eq 1
+        expect { resource.x 'blah' }.to raise_error Chef::Exceptions::ValidationFailed
+        expect(resource.x).to eq 10
+        expect(Namer.current_index).to eq 2
+      end
+    end
+  end
+
+  [ 'name_attribute', 'name_property' ].each do |name|
+    context "Chef::Resource::Property##{name}" do
+      with_property ":x, #{name}: true" do
+        it "defaults x to resource.name" do
+          expect(resource.x).to eq 'blah'
+        end
+        it "does not pick up resource.name if set" do
+          expect(resource.x 10).to eq 10
+          expect(resource.x).to eq 10
+        end
+        it "binds to the latest resource.name when run" do
+          resource.name 'foo'
+          expect(resource.x).to eq 'foo'
+        end
+        it "caches resource.name" do
+          expect(resource.x).to eq 'blah'
+          resource.name 'foo'
+          expect(resource.x).to eq 'blah'
+        end
+      end
+
+      with_property ":x, #{name}: false" do
+        it "defaults to nil" do
+          expect(resource.x).to be_nil
+        end
+      end
+
+      with_property ":x, #{name}: nil" do
+        it "defaults to nil" do
+          expect(resource.x).to be_nil
+        end
+      end
+
+      context "default ordering deprecation warnings" do
+        it "emits a deprecation warning for property :x, default: 10, #{name}: true" do
+          expect { resource_class.property :x, :default => 10, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError,
+            /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(default\) will be obeyed./
+        end
+        it "emits a deprecation warning for property :x, default: nil, #{name}: true" do
+          expect { resource_class.property :x, :default => nil, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError,
+            /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(name_property\) will be obeyed./
+        end
+        it "emits a deprecation warning for property :x, #{name}: true, default: 10" do
+          expect { resource_class.property :x, name.to_sym => true, :default => 10 }.to raise_error Chef::Exceptions::DeprecatedFeatureError,
+            /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(name_property\) will be obeyed./
+        end
+        it "emits a deprecation warning for property :x, #{name}: true, default: nil" do
+          expect { resource_class.property :x, name.to_sym => true, :default => nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError,
+            /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(name_property\) will be obeyed./
+        end
+      end
+
+      context "default ordering" do
+        before { Chef::Config[:treat_deprecation_warnings_as_errors] = false }
+        with_property ":x, default: 10, #{name}: true" do
+          it "chooses default over #{name}" do
+            expect(resource.x).to eq 10
+          end
+        end
+        with_property ":x, default: nil, #{name}: true" do
+          it "chooses #{name} over default" do
+            expect(resource.x).to eq 'blah'
+          end
+        end
+        with_property ":x, #{name}: true, default: 10" do
+          it "chooses #{name} over default" do
+            expect(resource.x).to eq 'blah'
+          end
+        end
+        with_property ":x, #{name}: true, default: nil" do
+          it "chooses #{name} over default" do
+            expect(resource.x).to eq 'blah'
+          end
+        end
+      end
+
+      context "default ordering when #{name} is nil" do
+        with_property ":x, #{name}: nil, default: 10" do
+          it "chooses default" do
+            expect(resource.x).to eq 10
+          end
+        end
+        with_property ":x, default: 10, #{name}: nil" do
+          it "chooses default" do
+            expect(resource.x).to eq 10
+          end
+        end
+      end
+
+      context "default ordering when #{name} is false" do
+        with_property ":x, #{name}: false, default: 10" do
+          it "chooses default" do
+            expect(resource.x).to eq 10
+          end
+        end
+        with_property ":x, default: 10, #{name}: nil" do
+          it "chooses default" do
+            expect(resource.x).to eq 10
+          end
+        end
+      end
+
+    end
+  end
+
+  it "raises an error if both name_property and name_attribute are specified" do
+    expect { resource_class.property :x, :name_property => false, :name_attribute => 1 }.to raise_error ArgumentError,
+      /Cannot specify both name_property and name_attribute together on property x of resource chef_resource_property_spec_(\d+)./
+    expect { resource_class.property :x, :name_property => false, :name_attribute => nil }.to raise_error ArgumentError,
+      /Cannot specify both name_property and name_attribute together on property x of resource chef_resource_property_spec_(\d+)./
+    expect { resource_class.property :x, :name_property => false, :name_attribute => false }.to raise_error ArgumentError,
+      /Cannot specify both name_property and name_attribute together on property x of resource chef_resource_property_spec_(\d+)./
+    expect { resource_class.property :x, :name_property => true, :name_attribute => true }.to raise_error ArgumentError,
+      /Cannot specify both name_property and name_attribute together on property x of resource chef_resource_property_spec_(\d+)./
+  end
+
+  context "with a custom property type" do
+    class CustomPropertyType < Chef::Property
+    end
+
+    with_property ":x, CustomPropertyType.new" do
+      it "creates x with the given type" do
+        expect(resource_class.properties[:x]).to be_kind_of(CustomPropertyType)
+      end
+
+      context "and a subclass" do
+        let(:subresource_class) do
+          new_resource_name = self.class.new_resource_name
+          Class.new(resource_class) do
+            resource_name new_resource_name
+          end
+        end
+        let(:subresource) do
+          subresource_class.new('blah')
+        end
+
+        context "with property :x, default: 10 on the subclass" do
+          before do
+            subresource_class.class_eval do
+              property :x, default: 10
+            end
+          end
+
+          it "x has the given type and default on the subclass" do
+            expect(subresource_class.properties[:x]).to be_kind_of(CustomPropertyType)
+            expect(subresource_class.properties[:x].default).to eq(10)
+          end
+
+          it "x does not have the default on the superclass" do
+            expect(resource_class.properties[:x]).to be_kind_of(CustomPropertyType)
+            expect(resource_class.properties[:x].default).to be_nil
+          end
+        end
+      end
+    end
+
+    with_property ":x, CustomPropertyType.new, default: 10" do
+      it "passes the default to the custom property type" do
+        expect(resource_class.properties[:x]).to be_kind_of(CustomPropertyType)
+        expect(resource_class.properties[:x].default).to eq(10)
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/deploy/revision_spec.rb b/spec/unit/provider/deploy/revision_spec.rb
index 4ca64e3..caa6087 100644
--- a/spec/unit/provider/deploy/revision_spec.rb
+++ b/spec/unit/provider/deploy/revision_spec.rb
@@ -21,7 +21,7 @@ require 'spec_helper'
 describe Chef::Provider::Deploy::Revision do
 
   before do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
     @temp_dir = Dir.mktmpdir
     Chef::Config[:file_cache_path] = @temp_dir
     @resource = Chef::Resource::Deploy.new("/my/deploy/dir")
diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb
index c95a9b3..adcb943 100644
--- a/spec/unit/provider/deploy_spec.rb
+++ b/spec/unit/provider/deploy_spec.rb
@@ -21,7 +21,7 @@ require 'spec_helper'
 describe Chef::Provider::Deploy do
 
   before do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
     @release_time = Time.utc( 2004, 8, 15, 16, 23, 42)
     allow(Time).to receive(:now).and_return(@release_time)
     @expected_release_dir = "/my/deploy/dir/releases/20040815162342"
@@ -356,13 +356,13 @@ describe Chef::Provider::Deploy do
   it "chowns the whole release dir to user and group specified in the resource" do
     @resource.user "foo"
     @resource.group "bar"
-    expect(FileUtils).to receive(:chown_R).with("foo", "bar", "/my/deploy/dir")
+    expect(FileUtils).to receive(:chown_R).with("foo", "bar", "/my/deploy/dir", { :force => true })
     @provider.enforce_ownership
   end
 
   it "skips the migration when resource.migrate => false but runs symlinks before migration" do
     @resource.migrate false
-    expect(@provider).not_to receive :run_command
+    expect(@provider).not_to receive :shell_out!
     expect(@provider).to receive :run_symlinks_before_migrate
     @provider.migrate
   end
@@ -378,7 +378,7 @@ describe Chef::Provider::Deploy do
 
     allow(STDOUT).to receive(:tty?).and_return(true)
     allow(Chef::Log).to receive(:info?).and_return(true)
-    expect(@provider).to receive(:run_command).with(:command => "migration_foo", :cwd => @expected_release_dir,
+    expect(@provider).to receive(:shell_out!).with("migration_foo",:cwd => @expected_release_dir,
                                                 :user => "deployNinja", :group => "deployNinjas",
                                                 :log_level => :info, :live_stream => STDOUT,
                                                 :log_tag => "deploy[/my/deploy/dir]",
@@ -445,13 +445,13 @@ describe Chef::Provider::Deploy do
   end
 
   it "does nothing for restart if restart_command is empty" do
-    expect(@provider).not_to receive(:run_command)
+    expect(@provider).not_to receive(:shell_out!)
     @provider.restart
   end
 
   it "runs the restart command in the current application dir when the resource has a restart_command" do
     @resource.restart_command "restartcmd"
-    expect(@provider).to receive(:run_command).with(:command => "restartcmd", :cwd => "/my/deploy/dir/current", :log_tag => "deploy[/my/deploy/dir]", :log_level => :debug)
+    expect(@provider).to receive(:shell_out!).with("restartcmd", :cwd => "/my/deploy/dir/current", :log_tag => "deploy[/my/deploy/dir]", :log_level => :debug)
     @provider.restart
   end
 
@@ -509,7 +509,7 @@ describe Chef::Provider::Deploy do
   it "shouldn't give a no method error on migrate if the environment is nil" do
     allow(@provider).to receive(:enforce_ownership)
     allow(@provider).to receive(:run_symlinks_before_migrate)
-    allow(@provider).to receive(:run_command)
+    allow(@provider).to receive(:shell_out!)
     @provider.migrate
 
   end
@@ -622,7 +622,7 @@ describe Chef::Provider::Deploy do
 
       gems = @provider.send(:gem_packages)
 
-      expect(gems.map { |g| g.action }).to eq([[:install]])
+      expect(gems.map { |g| g.action }).to eq([%i{install}])
       expect(gems.map { |g| g.name }).to eq(%w{eventmachine})
       expect(gems.map { |g| g.version }).to eq(%w{0.12.9})
     end
diff --git a/spec/unit/provider/directory_spec.rb b/spec/unit/provider/directory_spec.rb
index 13c57bf..4fad8c8 100644
--- a/spec/unit/provider/directory_spec.rb
+++ b/spec/unit/provider/directory_spec.rb
@@ -16,173 +16,272 @@
 # limitations under the License.
 #
 
-require 'ostruct'
-
 require 'spec_helper'
 require 'tmpdir'
 
 describe Chef::Provider::Directory do
-  before(:each) do
-    @new_resource = Chef::Resource::Directory.new(Dir.tmpdir)
-    if !windows?
-      @new_resource.owner(500)
-      @new_resource.group(500)
-      @new_resource.mode(0644)
+  let(:tmp_dir) { Dir.mktmpdir }
+  let(:new_resource) { Chef::Resource::Directory.new(tmp_dir) }
+  let(:node) { Chef::Node.new }
+  let(:events) { Chef::EventDispatch::Dispatcher.new }
+  let(:run_context) { Chef::RunContext.new(node, {}, events) }
+  let(:directory) { Chef::Provider::Directory.new(new_resource, run_context) }
+
+  describe "#load_current_resource" do
+    describe "scanning file security metadata"
+    describe "on unix", unix_only: true do
+      describe "when the directory exists" do
+        let(:dir_stat) { File::Stat.new(tmp_dir) }
+        let(:expected_uid) { dir_stat.uid }
+        let(:expected_gid) { dir_stat.gid }
+        let(:expected_mode) { "0%o" % ( dir_stat.mode & 007777 ) }
+        let(:expected_pwnam) { Etc.getpwuid(expected_uid).name }
+        let(:expected_grnam) { Etc.getgrgid(expected_gid).name }
+
+        it "describes the access mode as a String of octal integers" do
+          directory.load_current_resource
+          expect(directory.current_resource.mode).to eq(expected_mode)
+        end
+
+        it "when the new_resource.owner is numeric, describes the owner as a numeric uid" do
+          new_resource.owner(500)
+          directory.load_current_resource
+          expect(directory.current_resource.owner).to eql(expected_uid)
+        end
+
+        it "when the new_resource.group is numeric, describes the group as a numeric gid" do
+          new_resource.group(500)
+          directory.load_current_resource
+          expect(directory.current_resource.group).to eql(expected_gid)
+        end
+
+        it "when the new_resource.owner is a string, describes the owner as a string" do
+          new_resource.owner("foo")
+          directory.load_current_resource
+          expect(directory.current_resource.owner).to eql(expected_pwnam)
+        end
+
+        it "when the new_resource.group is a string, describes the group as a string" do
+          new_resource.group("bar")
+          directory.load_current_resource
+          expect(directory.current_resource.group).to eql(expected_grnam)
+        end
+      end
     end
-    @node = Chef::Node.new
-    @events = Chef::EventDispatch::Dispatcher.new
-    @run_context = Chef::RunContext.new(@node, {}, @events)
 
-    @directory = Chef::Provider::Directory.new(@new_resource, @run_context)
-  end
+    describe "on windows", windows_only: true do
+      describe "when the directory exists" do
+        it "the mode is always nil" do
+          directory.load_current_resource
+          expect(directory.current_resource.mode).to be nil
+        end
+
+        it "the owner is always nil" do
+          directory.load_current_resource
+          expect(directory.current_resource.owner).to be nil
+        end
 
+        it "the group is always nil" do
+          directory.load_current_resource
+          expect(directory.current_resource.group).to be nil
+        end
 
-  describe "scanning file security metadata on windows" do
-    before do
+        it "rights are always nil (incorrectly)" do
+          directory.load_current_resource
+          expect(directory.current_resource.rights).to be nil
+        end
+
+        it "inherits is always nil (incorrectly)" do
+          directory.load_current_resource
+          expect(directory.current_resource.inherits).to be nil
+        end
+      end
     end
 
-    it "describes the directory's access rights" do
-      skip
+    describe "when the directory does not exist" do
+      before do
+        FileUtils.rmdir tmp_dir
+      end
+
+      it "sets the mode, group and owner to nil" do
+        directory.load_current_resource
+        expect(directory.current_resource.mode).to eq(nil)
+        expect(directory.current_resource.group).to eq(nil)
+        expect(directory.current_resource.owner).to eq(nil)
+      end
     end
+
   end
 
-  describe "scanning file security metadata on unix" do
-    before do
-      allow(Chef::Platform).to receive(:windows?).and_return(false)
-    end
-    let(:mock_stat) do
-      cstats = double("stats")
-      allow(cstats).to receive(:uid).and_return(500)
-      allow(cstats).to receive(:gid).and_return(500)
-      allow(cstats).to receive(:mode).and_return(0755)
-      cstats
+  describe "#define_resource_requirements" do
+    describe "on unix", unix_only: true do
+      it "raises an exception if the user does not exist" do
+        new_resource.owner("arglebargle_iv")
+        expect(Etc).to receive(:getpwnam).with("arglebargle_iv").and_raise(ArgumentError)
+        directory.action = :create
+        directory.load_current_resource
+        expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original
+        directory.define_resource_requirements
+        expect { directory.process_resource_requirements }.to raise_error(ArgumentError)
+      end
+
+      it "raises an exception if the group does not exist" do
+        new_resource.group("arglebargle_iv")
+        expect(Etc).to receive(:getgrnam).with("arglebargle_iv").and_raise(ArgumentError)
+        directory.action = :create
+        directory.load_current_resource
+        expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original
+        directory.define_resource_requirements
+        expect { directory.process_resource_requirements }.to raise_error(ArgumentError)
+      end
     end
+  end
+
+  describe "#run_action(:create)" do
+    describe "when the directory exists" do
+      it "does not create the directory" do
+        expect(Dir).not_to receive(:mkdir).with(new_resource.path)
+        directory.run_action(:create)
+      end
 
-    it "describes the access mode as a String of octal integers" do
-      allow(File).to receive(:exists?).and_return(true)
-      expect(File).to receive(:stat).and_return(mock_stat)
-      @directory.load_current_resource
-      expect(@directory.current_resource.mode).to eq("0755")
+      it "should not set the resource as updated" do
+        directory.run_action(:create)
+        expect(new_resource).not_to be_updated
+      end
     end
 
-    context "when user and group are specified with UID/GID" do
-      it "describes the current owner and group as UID and GID" do
-        allow(File).to receive(:exists?).and_return(true)
-        expect(File).to receive(:stat).and_return(mock_stat)
-        @directory.load_current_resource
-        expect(@directory.current_resource.path).to eql(@new_resource.path)
-        expect(@directory.current_resource.owner).to eql(500)
-        expect(@directory.current_resource.group).to eql(500)
+    describe "when the directory does not exist" do
+      before do
+        FileUtils.rmdir tmp_dir
+      end
+
+      it "creates the directory" do
+        directory.run_action(:create)
+        expect(File.exist?(tmp_dir)).to be true
+      end
+
+      it "sets the new resource as updated" do
+        directory.run_action(:create)
+        expect(new_resource).to be_updated
       end
     end
 
-    context "when user/group are specified with user/group names" do
+    describe "when the parent directory does not exist" do
+      before do
+        new_resource.path "#{tmp_dir}/foobar"
+        FileUtils.rmdir tmp_dir
+      end
+
+      it "raises an exception when recursive is false" do
+        new_resource.recursive false
+        expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+      end
+
+      it "creates the directories when recursive is true" do
+        new_resource.recursive true
+        directory.run_action(:create)
+        expect(new_resource).to be_updated
+        expect(File.exist?("#{tmp_dir}/foobar")).to be true
+      end
+
+      it "raises an exception when the parent directory is a file and recursive is true" do
+        FileUtils.touch tmp_dir
+        new_resource.recursive true
+        expect { directory.run_action(:create) }.to raise_error
+      end
+
+      it "raises the right exception when the parent directory is a file and recursive is true" do
+        pending "this seems to return the wrong error"  # FIXME
+        FileUtils.touch tmp_dir
+        new_resource.recursive true
+        expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+      end
     end
-  end
 
-  # Unix only for now. While file security attribute reporting for windows is
-  # disabled, unix and windows differ in the number of exists? calls that are
-  # made by the provider.
-  it "should create a new directory on create, setting updated to true", :unix_only do
-    @new_resource.path "/tmp/foo"
+    describe "on OS X" do
+      before do
+        allow(node).to receive(:[]).with("platform").and_return('mac_os_x')
+        new_resource.path "/usr/bin/chef_test"
+        new_resource.recursive false
+        allow_any_instance_of(Chef::Provider::File).to receive(:do_selinux)
+      end
 
-    expect(File).to receive(:exists?).at_least(:once).and_return(false)
-    expect(File).to receive(:directory?).with("/tmp").and_return(true)
-    expect(Dir).to receive(:mkdir).with(@new_resource.path).once.and_return(true)
+      it "os x 10.10 can write to sip locations" do
+        allow(node).to receive(:[]).with("platform_version").and_return('10.10')
+        allow(Dir).to receive(:mkdir).and_return([true], [])
+        allow(::File).to receive(:directory?).and_return(true)
+        allow(Chef::FileAccessControl).to receive(:writable?).and_return(true)
+        directory.run_action(:create)
+        expect(new_resource).to be_updated
+      end
 
-    expect(@directory).to receive(:do_acl_changes)
-    allow(@directory).to receive(:do_selinux)
-    @directory.run_action(:create)
-    expect(@directory.new_resource).to be_updated
-  end
+      it "os x 10.11 cannot write to sip locations" do
+        allow(node).to receive(:[]).with("platform_version").and_return('10.11')
+        allow(::File).to receive(:directory?).and_return(true)
+        allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+        expect {directory.run_action(:create) }.to raise_error(Chef::Exceptions::InsufficientPermissions)
+      end
 
-  it "should raise an exception if the parent directory does not exist and recursive is false" do
-    @new_resource.path "/tmp/some/dir"
-    @new_resource.recursive false
-    expect { @directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+      it "os x 10.11 can write to sip exlcusions" do
+        new_resource.path "/usr/local/chef_test"
+        allow(node).to receive(:[]).with("platform_version").and_return('10.11')
+        allow(::File).to receive(:directory?).and_return(true)
+        allow(Dir).to receive(:mkdir).and_return([true], [])
+        allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+        directory.run_action(:create)
+        expect(new_resource).to be_updated
+      end
+    end
   end
 
-  # Unix only for now. While file security attribute reporting for windows is
-  # disabled, unix and windows differ in the number of exists? calls that are
-  # made by the provider.
-  it "should create a new directory when parent directory does not exist if recursive is true and permissions are correct", :unix_only do
-    @new_resource.path "/path/to/dir"
-    @new_resource.recursive true
-    expect(File).to receive(:exists?).with(@new_resource.path).ordered.and_return(false)
-
-    expect(File).to receive(:exists?).with('/path/to').ordered.and_return(false)
-    expect(File).to receive(:exists?).with('/path').ordered.and_return(true)
-    expect(Chef::FileAccessControl).to receive(:writable?).with('/path').ordered.and_return(true)
-    expect(File).to receive(:exists?).with(@new_resource.path).ordered.and_return(false)
-
-    expect(FileUtils).to receive(:mkdir_p).with(@new_resource.path).and_return(true)
-    expect(@directory).to receive(:do_acl_changes)
-    allow(@directory).to receive(:do_selinux)
-    @directory.run_action(:create)
-    expect(@new_resource).to be_updated
-  end
+  describe "#run_action(:create)" do
+    describe "when the directory exists" do
+      it "deletes the directory" do
+        directory.run_action(:delete)
+        expect(File.exist?(tmp_dir)).to be false
+      end
 
+      it "sets the new resource as updated" do
+        directory.run_action(:delete)
+        expect(new_resource).to be_updated
+      end
+    end
 
-  it "should raise an error when creating a directory when parent directory is a file" do
-    expect(File).to receive(:directory?).and_return(false)
-    expect(Dir).not_to receive(:mkdir).with(@new_resource.path)
-    expect { @directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
-    expect(@directory.new_resource).not_to be_updated
-  end
+    describe "when the directory does not exist" do
+      before do
+        FileUtils.rmdir tmp_dir
+      end
 
-  # Unix only for now. While file security attribute reporting for windows is
-  # disabled, unix and windows differ in the number of exists? calls that are
-  # made by the provider.
-  it "should not create the directory if it already exists", :unix_only do
-    stub_file_cstats
-    @new_resource.path "/tmp/foo"
-    expect(File).to receive(:directory?).at_least(:once).and_return(true)
-    expect(Chef::FileAccessControl).to receive(:writable?).with("/tmp").and_return(true)
-    expect(File).to receive(:exists?).at_least(:once).and_return(true)
-    expect(Dir).not_to receive(:mkdir).with(@new_resource.path)
-    expect(@directory).to receive(:do_acl_changes)
-    @directory.run_action(:create)
-  end
+      it "does not delete the directory" do
+        expect(Dir).not_to receive(:delete).with(new_resource.path)
+        directory.run_action(:delete)
+      end
 
-  it "should delete the directory if it exists, and is writable with action_delete" do
-    expect(File).to receive(:directory?).and_return(true)
-    expect(Chef::FileAccessControl).to receive(:writable?).once.and_return(true)
-    expect(Dir).to receive(:delete).with(@new_resource.path).once.and_return(true)
-    @directory.run_action(:delete)
-  end
+      it "sets the new resource as updated" do
+        directory.run_action(:delete)
+        expect(new_resource).not_to be_updated
+      end
+    end
 
-  it "should raise an exception if it cannot delete the directory due to bad permissions" do
-    allow(File).to receive(:exists?).and_return(true)
-    allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
-    expect {  @directory.run_action(:delete) }.to raise_error(RuntimeError)
-  end
+    describe "when the directory is not writable" do
+      before do
+        allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+      end
 
-  it "should take no action when deleting a target directory that does not exist" do
-    @new_resource.path "/an/invalid/path"
-    allow(File).to receive(:exists?).and_return(false)
-    expect(Dir).not_to receive(:delete).with(@new_resource.path)
-    @directory.run_action(:delete)
-    expect(@directory.new_resource).not_to be_updated
-  end
+      it "cannot delete it and raises an exception" do
+        expect {  directory.run_action(:delete) }.to raise_error(RuntimeError)
+      end
+    end
 
-  it "should raise an exception when deleting a directory when target directory is a file" do
-    stub_file_cstats
-    @new_resource.path "/an/invalid/path"
-    allow(File).to receive(:exists?).and_return(true)
-    expect(File).to receive(:directory?).and_return(false)
-    expect(Dir).not_to receive(:delete).with(@new_resource.path)
-    expect { @directory.run_action(:delete) }.to raise_error(RuntimeError)
-    expect(@directory.new_resource).not_to be_updated
-  end
+    describe "when the target directory is a file" do
+      before do
+        FileUtils.rmdir tmp_dir
+        FileUtils.touch tmp_dir
+      end
 
-  def stub_file_cstats
-    cstats = double("stats")
-    allow(cstats).to receive(:uid).and_return(500)
-    allow(cstats).to receive(:gid).and_return(500)
-    allow(cstats).to receive(:mode).and_return(0755)
-    # File.stat is called in:
-    # - Chef::Provider::File.load_current_resource_attrs
-    # - Chef::ScanAccessControl via Chef::Provider::File.setup_acl
-    allow(File).to receive(:stat).and_return(cstats)
+      it "cannot delete it and raises an exception" do
+        expect {  directory.run_action(:delete) }.to raise_error(RuntimeError)
+      end
+    end
   end
 end
diff --git a/spec/unit/provider/dsc_resource_spec.rb b/spec/unit/provider/dsc_resource_spec.rb
index 0a6c22b..46cdbd9 100644
--- a/spec/unit/provider/dsc_resource_spec.rb
+++ b/spec/unit/provider/dsc_resource_spec.rb
@@ -16,7 +16,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
 require 'chef'
 require 'spec_helper'
 
@@ -34,51 +33,84 @@ describe Chef::Provider::DscResource do
       node.automatic[:languages][:powershell][:version] = '4.0'
       node
     }
-
-    it 'raises a NoProviderAvailable exception' do
+    it 'raises a ProviderNotFound exception' do
       expect(provider).not_to receive(:meta_configuration)
       expect{provider.run_action(:run)}.to raise_error(
-              Chef::Exceptions::NoProviderAvailable, /5\.0\.10018\.0/)
+              Chef::Exceptions::ProviderNotFound, /5\.0\.10018\.0/)
     end
   end
 
   context 'when Powershell supports Invoke-DscResource' do
+
+    context 'when RefreshMode is not set to Disabled' do
+      context 'and the WMF 5 is a preview release' do
+        let (:node) {
+          node = Chef::Node.new
+          node.automatic[:languages][:powershell][:version] = '5.0.10018.0'
+          node
+        }
+        it 'raises an exception' do
+          expect(provider).to receive(:dsc_refresh_mode_disabled?).and_return(false)
+          expect { provider.run_action(:run) }.to raise_error(
+            Chef::Exceptions::ProviderNotFound, /Disabled/)
+        end
+      end
+      context 'and the WMF is 5 RTM or newer' do
+        let (:node) {
+          node = Chef::Node.new
+          node.automatic[:languages][:powershell][:version] = '5.0.10586.0'
+          node
+        }
+        it 'does not raises an exception' do
+          expect(provider).to receive(:test_resource)
+          expect(provider).to receive(:set_resource)
+          expect(provider).to receive(:reboot_if_required)
+          expect { provider.run_action(:run) }.to_not raise_error
+        end
+      end
+    end
+  end
+  
+  context 'when the LCM supports Invoke-DscResource' do
     let (:node) {
       node = Chef::Node.new
       node.automatic[:languages][:powershell][:version] = '5.0.10018.0'
       node
     }
 
-    context 'when RefreshMode is not set to Disabled' do
-      let (:meta_configuration) { {'RefreshMode' => 'AnythingElse'}}
-
-      it 'raises an exception' do
-        expect(provider).to receive(:meta_configuration).and_return(
-                                                             meta_configuration)
-        expect { provider.run_action(:run) }.to raise_error(
-          Chef::Exceptions::NoProviderAvailable, /Disabled/)
-      end
+    it 'does not update the resource if it is up to date' do
+      expect(provider).to receive(:dsc_refresh_mode_disabled?).and_return(true)
+      expect(provider).to receive(:test_resource).and_return(true)
+      provider.run_action(:run)
+      expect(resource).not_to be_updated
     end
 
-    context 'when RefreshMode is set to Disabled' do
-      let (:meta_configuration) { {'RefreshMode' => 'Disabled'}}
-
-      it 'does not update the resource if it is up to date' do
-        expect(provider).to receive(:meta_configuration).and_return(
-                                                             meta_configuration)
-        expect(provider).to receive(:test_resource).and_return(true)
-        provider.run_action(:run)
-        expect(resource).not_to be_updated
-      end
-
-      it 'converges the resource if it is not up to date' do
-        expect(provider).to receive(:meta_configuration).and_return(
-                                                             meta_configuration)
-        expect(provider).to receive(:test_resource).and_return(false)
-        expect(provider).to receive(:set_resource)
-        provider.run_action(:run)
-        expect(resource).to be_updated
-      end
+    it 'converges the resource if it is not up to date' do
+      expect(provider).to receive(:dsc_refresh_mode_disabled?).and_return(true)
+      expect(provider).to receive(:test_resource).and_return(false)
+      expect(provider).to receive(:set_resource)
+      provider.run_action(:run)
+      expect(resource).to be_updated
+    end
+    
+    it 'flags the resource as reboot required when required' do
+      expect(provider).to receive(:dsc_refresh_mode_disabled?).and_return(true)
+      expect(provider).to receive(:test_resource).and_return(false)
+      expect(provider).to receive(:invoke_resource).
+        and_return(double(:stdout => '', :return_value =>nil))
+      expect(provider).to receive(:return_dsc_resource_result).and_return(true)
+      expect(provider).to receive(:create_reboot_resource)
+      provider.run_action(:run)
+    end
+    
+    it 'does not flag the resource as reboot required when not required' do
+      expect(provider).to receive(:dsc_refresh_mode_disabled?).and_return(true)
+      expect(provider).to receive(:test_resource).and_return(false)
+      expect(provider).to receive(:invoke_resource).
+        and_return(double(:stdout => '', :return_value =>nil))
+      expect(provider).to receive(:return_dsc_resource_result).and_return(false)
+      expect(provider).to_not receive(:create_reboot_resource)
+      provider.run_action(:run)
     end
   end
-end
+end
\ No newline at end of file
diff --git a/spec/unit/provider/dsc_script_spec.rb b/spec/unit/provider/dsc_script_spec.rb
index d4b2eb3..76589e7 100644
--- a/spec/unit/provider/dsc_script_spec.rb
+++ b/spec/unit/provider/dsc_script_spec.rb
@@ -158,14 +158,14 @@ describe Chef::Provider::DscScript do
 
           expect {
             provider.run_action(:run)
-          }.to raise_error(Chef::Exceptions::NoProviderAvailable)
+          }.to raise_error(Chef::Exceptions::ProviderNotFound)
         end
       end
 
       it 'raises an exception if Powershell is not present' do
         expect {
           provider.run_action(:run)
-        }.to raise_error(Chef::Exceptions::NoProviderAvailable)
+        }.to raise_error(Chef::Exceptions::ProviderNotFound)
       end
 
     end
diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb
index 51305b6..3af35a1 100644
--- a/spec/unit/provider/execute_spec.rb
+++ b/spec/unit/provider/execute_spec.rb
@@ -25,28 +25,32 @@ describe Chef::Provider::Execute do
   let(:run_context) { Chef::RunContext.new(node, {}, events) }
   let(:provider) { Chef::Provider::Execute.new(new_resource, run_context) }
   let(:current_resource) { Chef::Resource::Ifconfig.new("foo_resource", run_context) }
+  # You will be the same object, I promise.
+  @live_stream = Chef::EventDispatch::EventsOutputStream.new(run_context.events, :name => :execute)
 
   let(:opts) do
     {
       timeout:      3600,
       returns:      0,
       log_level:    :info,
-      log_tag:      new_resource.to_s,
-      live_stream:  STDOUT,
+      log_tag:      new_resource.to_s
     }
   end
 
   let(:new_resource) { Chef::Resource::Execute.new("foo_resource", run_context) }
 
   before do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(Chef::EventDispatch::EventsOutputStream).to receive(:new) { @live_stream }
+    allow(ChefConfig).to receive(:windows?) { false }
     @original_log_level = Chef::Log.level
     Chef::Log.level = :info
-    allow(STDOUT).to receive(:tty?).and_return(true)
+    allow(STDOUT).to receive(:tty?).and_return(false)
   end
 
   after do
     Chef::Log.level = @original_log_level
+    Chef::Config[:always_stream_execute] = false
+    Chef::Config[:daemon] = false
   end
 
   describe "#initialize" do
@@ -142,35 +146,98 @@ describe Chef::Provider::Execute do
       expect(new_resource).not_to be_updated
     end
 
-    it "should unset the live_stream if STDOUT is not a tty" do
-      expect(STDOUT).to receive(:tty?).and_return(false)
+    it "should not include stdout/stderr in failure exception for sensitive resource" do
       opts.delete(:live_stream)
-      expect(provider).to receive(:shell_out!).with(new_resource.name, opts)
-      expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original
-      expect(Chef::Log).not_to receive(:warn)
-      provider.run_action(:run)
-      expect(new_resource).to be_updated
+      new_resource.sensitive true
+      expect(provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      expect do
+        provider.run_action(:run)
+      end.to raise_error(Mixlib::ShellOut::ShellCommandFailed, /suppressed for sensitive resource/)
     end
 
-    it "should unset the live_stream if chef is running as a daemon" do
-      allow(Chef::Config).to receive(:[]).and_call_original
-      expect(Chef::Config).to receive(:[]).with(:daemon).and_return(true)
-      opts.delete(:live_stream)
-      expect(provider).to receive(:shell_out!).with(new_resource.name, opts)
-      expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original
-      expect(Chef::Log).not_to receive(:warn)
-      provider.run_action(:run)
-      expect(new_resource).to be_updated
-    end
+    describe "streaming output" do
+      it "should not set the live_stream if sensitive is on" do
+        new_resource.sensitive true
+        expect(provider).to receive(:shell_out!).with(new_resource.name, opts)
+        expect(provider).to receive(:converge_by).with("execute sensitive resource").and_call_original
+        expect(Chef::Log).not_to receive(:warn)
+        provider.run_action(:run)
+        expect(new_resource).to be_updated
+      end
 
-    it "should unset the live_stream if we are not running with a log level of at least :info" do
-      expect(Chef::Log).to receive(:info?).and_return(false)
-      opts.delete(:live_stream)
-      expect(provider).to receive(:shell_out!).with(new_resource.name, opts)
-      expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original
-      expect(Chef::Log).not_to receive(:warn)
-      provider.run_action(:run)
-      expect(new_resource).to be_updated
+      describe "with an output formatter listening" do
+        let(:events) { d = Chef::EventDispatch::Dispatcher.new; d.register(Chef::Formatters::Doc.new(StringIO.new, StringIO.new)); d }
+
+        before do
+          Chef::Config[:stream_execute_output] = true
+        end
+
+        it "should set the live_stream if the log level is info or above" do
+          nopts = opts
+          nopts[:live_stream] = @live_stream
+          expect(provider).to receive(:shell_out!).with(new_resource.name, nopts)
+          expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original
+          expect(Chef::Log).not_to receive(:warn)
+          provider.run_action(:run)
+          expect(new_resource).to be_updated
+        end
+
+        it "should set the live_stream if the resource requests live streaming" do
+          Chef::Log.level = :warn
+          new_resource.live_stream true
+          nopts = opts
+          nopts[:live_stream] = @live_stream
+          expect(provider).to receive(:shell_out!).with(new_resource.name, nopts)
+          expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original
+          expect(Chef::Log).not_to receive(:warn)
+          provider.run_action(:run)
+          expect(new_resource).to be_updated
+        end
+
+        it "should not set the live_stream if the resource is sensitive" do
+          new_resource.sensitive true
+          expect(provider).to receive(:shell_out!).with(new_resource.name, opts)
+          expect(provider).to receive(:converge_by).with("execute sensitive resource").and_call_original
+          expect(Chef::Log).not_to receive(:warn)
+          provider.run_action(:run)
+          expect(new_resource).to be_updated
+        end
+      end
+
+      describe "with only logging enabled" do
+        it "should set the live_stream to STDOUT if we are a TTY, not daemonized, not sensitive, and info is enabled" do
+          nopts = opts
+          nopts[:live_stream] = STDOUT
+          allow(STDOUT).to receive(:tty?).and_return(true)
+          expect(provider).to receive(:shell_out!).with(new_resource.name, nopts)
+          expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original
+          expect(Chef::Log).not_to receive(:warn)
+          provider.run_action(:run)
+          expect(new_resource).to be_updated
+        end
+
+        it "should not set the live_stream to STDOUT if we are a TTY, not daemonized, but sensitive" do
+          new_resource.sensitive true
+          allow(STDOUT).to receive(:tty?).and_return(true)
+          expect(provider).to receive(:shell_out!).with(new_resource.name, opts)
+          expect(provider).to receive(:converge_by).with("execute sensitive resource").and_call_original
+          expect(Chef::Log).not_to receive(:warn)
+          provider.run_action(:run)
+          expect(new_resource).to be_updated
+        end
+
+        it "should not set the live_stream to STDOUT if we are a TTY, but daemonized" do
+          Chef::Config[:daemon] = true
+          allow(STDOUT).to receive(:tty?).and_return(true)
+          expect(provider).to receive(:shell_out!).with(new_resource.name, opts)
+          expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original
+          expect(Chef::Log).not_to receive(:warn)
+          provider.run_action(:run)
+          expect(new_resource).to be_updated
+        end
+
+      end
     end
+
   end
 end
diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb
index 351e734..0c02ae9 100644
--- a/spec/unit/provider/ifconfig/debian_spec.rb
+++ b/spec/unit/provider/ifconfig/debian_spec.rb
@@ -144,11 +144,6 @@ EOF
           expect(IO.read(tempfile.path)).to eq(expected_string)
         end
 
-        it "should not mark the resource as updated" do
-          provider.run_action(:add)
-          pending "superclass ifconfig provider is not idempotent"
-          expect(new_resource.updated_by_last_action?).to be_falsey
-        end
       end
 
       context "when the /etc/network/interfaces file does not have the source line" do
@@ -280,11 +275,6 @@ another line
             expect(IO.read(tempfile.path)).to eq(expected_string)
           end
 
-          it "should not mark the resource as updated" do
-            provider.run_action(:add)
-            pending "superclass ifconfig provider is not idempotent"
-            expect(new_resource.updated_by_last_action?).to be_falsey
-          end
         end
 
         context "when the /etc/network/interfaces file does not have the source line" do
diff --git a/spec/unit/provider/ifconfig_spec.rb b/spec/unit/provider/ifconfig_spec.rb
index d290ab7..4940f19 100644
--- a/spec/unit/provider/ifconfig_spec.rb
+++ b/spec/unit/provider/ifconfig_spec.rb
@@ -46,7 +46,7 @@ describe Chef::Provider::Ifconfig do
       allow(@provider).to receive(:shell_out).and_return(@status)
       @provider.load_current_resource
     end
-    it "should track state of ifconfig failure." do
+    it "should track state of ifconfig failure" do
       expect(@provider.instance_variable_get("@status").exitstatus).not_to eq(0)
     end
     it "should thrown an exception when ifconfig fails" do
@@ -68,6 +68,16 @@ describe Chef::Provider::Ifconfig do
       expect(@new_resource).to be_updated
     end
 
+    it "should set the address to target if specified" do
+      allow(@provider).to receive(:load_current_resource)
+      @new_resource.target "172.16.32.2"
+      command = "ifconfig eth0 172.16.32.2 netmask 255.255.254.0 metric 1 mtu 1500"
+      expect(@provider).to receive(:run_command).with(:command => command)
+
+      @provider.run_action(:add)
+      expect(@new_resource).to be_updated
+    end
+
     it "should not add an interface if it already exists" do
       allow(@provider).to receive(:load_current_resource)
       expect(@provider).not_to receive(:run_command)
@@ -85,7 +95,7 @@ describe Chef::Provider::Ifconfig do
 
   describe Chef::Provider::Ifconfig, "action_enable" do
 
-    it "should enable interface if does not exist" do
+    it "should enable interface if it does not exist" do
       allow(@provider).to receive(:load_current_resource)
       @current_resource.inet_addr nil
       command = "ifconfig eth0 10.0.0.1 netmask 255.255.254.0 metric 1 mtu 1500"
@@ -96,6 +106,16 @@ describe Chef::Provider::Ifconfig do
       expect(@new_resource).to be_updated
     end
 
+    it "should set the address to target if specified" do
+      allow(@provider).to receive(:load_current_resource)
+      @new_resource.target "172.16.32.2"
+      command = "ifconfig eth0 172.16.32.2 netmask 255.255.254.0 metric 1 mtu 1500"
+      expect(@provider).to receive(:run_command).with(:command => command)
+
+      @provider.run_action(:enable)
+      expect(@new_resource).to be_updated
+    end
+
     it "should not enable interface if it already exists" do
       allow(@provider).to receive(:load_current_resource)
       expect(@provider).not_to receive(:run_command)
diff --git a/spec/unit/provider/mount/aix_spec.rb b/spec/unit/provider/mount/aix_spec.rb
index ca0ddd0..e232592 100644
--- a/spec/unit/provider/mount/aix_spec.rb
+++ b/spec/unit/provider/mount/aix_spec.rb
@@ -126,9 +126,10 @@ ENABLED
       @provider.run_action(:mount)
     end
 
-    it "should not mount resource if it is already mounted" do
+    it "should not mount resource if it is already mounted and the options have not changed" do
       stub_mounted_enabled(@provider, @mounted_output, "")
 
+      allow(@provider).to receive(:mount_options_unchanged?).and_return(true)
       expect(@provider).not_to receive(:mount_fs)
 
       @provider.run_action(:mount)
diff --git a/spec/unit/provider/mount/mount_spec.rb b/spec/unit/provider/mount/mount_spec.rb
index 7a37ffe..dd13a62 100644
--- a/spec/unit/provider/mount/mount_spec.rb
+++ b/spec/unit/provider/mount/mount_spec.rb
@@ -323,6 +323,12 @@ describe Chef::Provider::Mount::Mount do
         @provider.mount_fs()
       end
 
+      it "should not mount the filesystem if it is mounted and the options have not changed" do
+        allow(@current_resource).to receive(:mounted).and_return(true)
+        expect(@provider).to_not receive(:shell_out!)
+        @provider.mount_fs()
+      end
+
     end
 
     describe "umount_fs" do
diff --git a/spec/unit/provider/mount/windows_spec.rb b/spec/unit/provider/mount/windows_spec.rb
index 467d923..2de6f11 100644
--- a/spec/unit/provider/mount/windows_spec.rb
+++ b/spec/unit/provider/mount/windows_spec.rb
@@ -111,6 +111,20 @@ describe Chef::Provider::Mount::Windows do
         allow(@current_resource).to receive(:mounted).and_return(true)
         @provider.mount_fs
       end
+
+      it "should remount the filesystem if it is mounted and the options have changed" do
+        expect(@vol).to receive(:add).with(:remote => @new_resource.device,
+                                       :username => @new_resource.username,
+                                       :domainname => @new_resource.domain,
+                                       :password => @new_resource.password)
+        @provider.mount_fs
+      end
+
+      it "should not mount the filesystem if it is mounted and the options have not changed" do
+        expect(@vol).to_not receive(:add)
+        allow(@current_resource).to receive(:mounted).and_return(true)
+        @provider.mount_fs
+      end
     end
 
     describe "when unmounting a file system" do
diff --git a/spec/unit/provider/mount_spec.rb b/spec/unit/provider/mount_spec.rb
index e9fe3fa..cc2a456 100644
--- a/spec/unit/provider/mount_spec.rb
+++ b/spec/unit/provider/mount_spec.rb
@@ -61,8 +61,19 @@ describe Chef::Provider::Mount do
       expect(new_resource).to be_updated_by_last_action
     end
 
-    it "should not mount the filesystem if it is mounted" do
+    it "should remount the filesystem if it is mounted and the options have changed" do
       allow(current_resource).to receive(:mounted).and_return(true)
+      allow(provider).to receive(:mount_options_unchanged?).and_return(false)
+      expect(provider).to receive(:umount_fs).and_return(true)
+      expect(provider).to receive(:wait_until_unmounted)
+      expect(provider).to receive(:mount_fs).and_return(true)
+      provider.run_action(:mount)
+      expect(new_resource).to be_updated_by_last_action
+    end
+
+    it "should not mount the filesystem if it is mounted and the options have not changed" do
+      allow(current_resource).to receive(:mounted).and_return(true)
+      expect(provider).to receive(:mount_options_unchanged?).and_return(true)
       expect(provider).not_to receive(:mount_fs)
       provider.run_action(:mount)
       expect(new_resource).not_to be_updated_by_last_action
diff --git a/spec/unit/provider/package/aix_spec.rb b/spec/unit/provider/package/aix_spec.rb
index 5bc861b..13992cb 100644
--- a/spec/unit/provider/package/aix_spec.rb
+++ b/spec/unit/provider/package/aix_spec.rb
@@ -36,23 +36,27 @@ describe Chef::Provider::Package::Aix do
      @bffinfo ="/usr/lib/objrepos:samba.base:3.3.12.0::COMMITTED:I:Samba for AIX:
  /etc/objrepos:samba.base:3.3.12.0::COMMITTED:I:Samba for AIX:"
 
-      @status = double("Status", :stdout => "", :exitstatus => 0)
+     @empty_status = double("Status", :stdout => "", :exitstatus => 0)
     end
 
     it "should create a current resource with the name of new_resource" do
-      allow(@provider).to receive(:shell_out).and_return(@status)
+      status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
+      expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+      expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status)
       @provider.load_current_resource
       expect(@provider.current_resource.name).to eq("samba.base")
     end
 
     it "should set the current resource bff package name to the new resource bff package name" do
-      allow(@provider).to receive(:shell_out).and_return(@status)
+      status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
+      expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+      expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status)
       @provider.load_current_resource
       expect(@provider.current_resource.package_name).to eq("samba.base")
     end
 
     it "should raise an exception if a source is supplied but not found" do
-      allow(@provider).to receive(:shell_out).and_return(@status)
+      allow(@provider).to receive(:shell_out).and_return(@empty_status)
       allow(::File).to receive(:exists?).and_return(false)
       @provider.load_current_resource
       @provider.define_resource_requirements
@@ -61,8 +65,8 @@ describe Chef::Provider::Package::Aix do
 
     it "should get the source package version from lslpp if provided" do
       status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
-      expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base").and_return(status)
-      expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base").and_return(@status)
+      expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+      expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status)
       @provider.load_current_resource
 
       expect(@provider.current_resource.package_name).to eq("samba.base")
@@ -73,8 +77,8 @@ describe Chef::Provider::Package::Aix do
       status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
       @stdout = StringIO.new(@bffinfo)
       @stdin, @stderr = StringIO.new, StringIO.new
-      expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base").and_return(@status)
-      expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base").and_return(status)
+      expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+      expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(status)
       @provider.load_current_resource
       expect(@provider.current_resource.version).to eq("3.3.12.0")
     end
@@ -94,12 +98,20 @@ describe Chef::Provider::Package::Aix do
     end
 
     it "should return a current resource with a nil version if the package is not found" do
-      status = double(:stdout => "", :exitstatus => 0)
-      expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base").and_return(status)
-      expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base").and_return(status)
+      status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
+      expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+      expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status)
       @provider.load_current_resource
       expect(@provider.current_resource.version).to be_nil
     end
+
+    it "should raise an exception if the source doesn't provide the requested package" do
+      wrongbffinfo = "/usr/lib/objrepos:openssl.base:0.9.8.2400::COMMITTED:I:Open Secure Socket Layer:
+/etc/objrepos:openssl.base:0.9.8.2400::COMMITTED:I:Open Secure Socket Layer:"
+      status = double("Status", :stdout => wrongbffinfo, :exitstatus => 0)
+      expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+      expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package)
+    end
   end
 
   describe "candidate_version" do
@@ -125,7 +137,7 @@ describe Chef::Provider::Package::Aix do
 
   describe "install and upgrade" do
     it "should run installp -aYF -d with the package source to install" do
-      expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base samba.base")
+      expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base samba.base", timeout: 900)
       @provider.install_package("samba.base", "3.3.12.0")
     end
 
@@ -133,26 +145,26 @@ describe Chef::Provider::Package::Aix do
       @new_resource = Chef::Resource::Package.new("/tmp/samba.base")
       @provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context)
       expect(@new_resource.source).to eq("/tmp/samba.base")
-      expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base /tmp/samba.base")
+      expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base /tmp/samba.base", timeout: 900)
       @provider.install_package("/tmp/samba.base", "3.3.12.0")
     end
 
     it "should run installp with -eLogfile option." do
       allow(@new_resource).to receive(:options).and_return("-e/tmp/installp.log")
-      expect(@provider).to receive(:shell_out!).with("installp -aYF  -e/tmp/installp.log -d /tmp/samba.base samba.base")
+      expect(@provider).to receive(:shell_out!).with("installp -aYF  -e/tmp/installp.log -d /tmp/samba.base samba.base", timeout: 900)
       @provider.install_package("samba.base", "3.3.12.0")
     end
   end
 
   describe "remove" do
     it "should run installp -u samba.base to remove the package" do
-      expect(@provider).to receive(:shell_out!).with("installp -u samba.base")
+      expect(@provider).to receive(:shell_out!).with("installp -u samba.base", timeout: 900)
       @provider.remove_package("samba.base", "3.3.12.0")
     end
 
     it "should run installp -u -e/tmp/installp.log  with options -e/tmp/installp.log" do
       allow(@new_resource).to receive(:options).and_return("-e/tmp/installp.log")
-      expect(@provider).to receive(:shell_out!).with("installp -u  -e/tmp/installp.log samba.base")
+      expect(@provider).to receive(:shell_out!).with("installp -u  -e/tmp/installp.log samba.base", timeout: 900)
       @provider.remove_package("samba.base", "3.3.12.0")
     end
 
diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb
index 3fd8621..5b77e77 100644
--- a/spec/unit/provider/package/dpkg_spec.rb
+++ b/spec/unit/provider/package/dpkg_spec.rb
@@ -1,4 +1,3 @@
-#
 # Author:: Bryan McLellan (btm at loftninjas.org)
 # Copyright:: Copyright (c) 2009 Bryan McLellan
 # License:: Apache License, Version 2.0
@@ -19,43 +18,109 @@
 require 'spec_helper'
 
 describe Chef::Provider::Package::Dpkg do
+  let(:node) { Chef::Node.new }
+  let(:events) { Chef::EventDispatch::Dispatcher.new }
+  let(:run_context) { Chef::RunContext.new(node, {}, events) }
+  let(:package) { "wget" }
+  let(:source) { "/tmp/wget_1.11.4-1ubuntu1_amd64.deb" }
+  let(:new_resource) do
+    new_resource = Chef::Resource::DpkgPackage.new(package)
+    new_resource.source source
+    new_resource
+  end
+  let(:provider) { Chef::Provider::Package::Dpkg.new(new_resource, run_context) }
+
+  let(:dpkg_deb_version) { "1.11.4" }
+  let(:dpkg_deb_status) { status = double(:stdout => "#{package}\t#{dpkg_deb_version}", :exitstatus => 0) }
+  let(:dpkg_s_version) { "1.11.4-1ubuntu1" }
+  let(:dpkg_s_status) do
+    stdout = <<-DPKG_S
+Package: #{package}
+Status: install ok installed
+Priority: important
+Section: web
+Installed-Size: 1944
+Maintainer: Ubuntu Core developers <ubuntu-devel-discuss at lists.ubuntu.com>
+Architecture: amd64
+Version: #{dpkg_s_version}
+Config-Version: #{dpkg_s_version}
+Depends: libc6 (>= 2.8~20080505), libssl0.9.8 (>= 0.9.8f-5)
+Conflicts: wget-ssl
+    DPKG_S
+    status = double(:stdout => stdout, :exitstatus => 1)
+  end
+
   before(:each) do
-    @node = Chef::Node.new
-    @events = Chef::EventDispatch::Dispatcher.new
-    @run_context = Chef::RunContext.new(@node, {}, @events)
-    @new_resource = Chef::Resource::Package.new("wget")
-    @new_resource.source "/tmp/wget_1.11.4-1ubuntu1_amd64.deb"
+    allow(provider).to receive(:shell_out!).with("dpkg-deb -W #{source}", timeout: 900).and_return(dpkg_deb_status)
+    allow(provider).to receive(:shell_out!).with("dpkg -s #{package}", timeout: 900, returns: [0,1]).and_return(double(stdout: "", exitstatus: 1))
+    allow(::File).to receive(:exist?).with(source).and_return(true)
+  end
 
-    @provider = Chef::Provider::Package::Dpkg.new(@new_resource, @run_context)
+  describe "#define_resource_requirements" do
+    it "should raise an exception if a source is supplied but not found when :install" do
+      allow(::File).to receive(:exist?).with(source).and_return(false)
+      expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should raise an exception if a source is supplied but not found when :upgrade" do
+      allow(::File).to receive(:exist?).with(source).and_return(false)
+      expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
+    end
+
+    # FIXME?  we're saying we ignore source, but should supplying source on :remove or :purge be an actual error?
+    it "should not raise an exception if a source is supplied but not found when :remove" do
+      allow(::File).to receive(:exist?).with(source).and_return(false)
+      expect(provider).to receive(:action_remove)
+      expect { provider.run_action(:remove) }.not_to raise_error
+    end
+
+    it "should not raise an exception if a source is supplied but not found when :purge" do
+      allow(::File).to receive(:exist?).with(source).and_return(false)
+      expect(provider).to receive(:action_purge)
+      expect { provider.run_action(:purge) }.not_to raise_error
+    end
 
-    @status = double(:stdout => "", :exitstatus => 0)
-    allow(@provider).to receive(:shell_out).and_return(@status)
+    it "should raise an exception if a source is nil when :install" do
+      new_resource.source nil
+      allow(::File).to receive(:exist?).with(source).and_return(false)
+      expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should raise an exception if a source is nil when :upgrade" do
+      new_resource.source nil
+      allow(::File).to receive(:exist?).with(source).and_return(false)
+      expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
+    end
 
-    allow(::File).to receive(:exists?).and_return(true)
+    it "should not raise an exception if a source is nil when :remove" do
+      new_resource.source nil
+      allow(::File).to receive(:exist?).with(source).and_return(false)
+      expect(provider).to receive(:action_remove)
+      expect { provider.run_action(:remove) }.not_to raise_error
+    end
+
+    it "should not raise an exception if a source is nil when :purge" do
+      new_resource.source nil
+      allow(::File).to receive(:exist?).with(source).and_return(false)
+      expect(provider).to receive(:action_purge)
+      expect { provider.run_action(:purge) }.not_to raise_error
+    end
   end
 
   describe "when loading the current resource state" do
 
     it "should create a current resource with the name of the new_resource" do
-      @provider.load_current_resource
-      expect(@provider.current_resource.package_name).to eq("wget")
-    end
-
-    it "should raise an exception if a source is supplied but not found" do
-      @provider.load_current_resource
-      @provider.define_resource_requirements
-      allow(::File).to receive(:exists?).and_return(false)
-      expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+      provider.load_current_resource
+      expect(provider.current_resource.package_name).to eq(["wget"])
     end
 
     describe 'gets the source package version from dpkg-deb' do
       def check_version(version)
-        @status = double(:stdout => "wget\t#{version}", :exitstatus => 0)
-        allow(@provider).to receive(:shell_out).with("dpkg-deb -W #{@new_resource.source}").and_return(@status)
-        @provider.load_current_resource
-        expect(@provider.current_resource.package_name).to eq("wget")
-        expect(@new_resource.version).to eq(version)
-        expect(@provider.candidate_version).to eq(version)
+        status = double(:stdout => "wget\t#{version}", :exitstatus => 0)
+        expect(provider).to receive(:shell_out!).with("dpkg-deb -W #{source}", timeout: 900).and_return(status)
+        provider.load_current_resource
+        expect(provider.current_resource.package_name).to eq(["wget"])
+        expect(provider.candidate_version).to eq([version])
       end
 
       it 'if short version provided' do
@@ -75,121 +140,152 @@ describe Chef::Provider::Package::Dpkg do
       end
     end
 
-    it "gets the source package name from dpkg-deb correctly when the package name has `-', `+' or `.' characters" do
-      stdout = "f.o.o-pkg++2\t1.11.4-1ubuntu1"
-      status = double(:stdout => stdout, :exitstatus => 1)
-      allow(@provider).to receive(:shell_out).and_return(status)
-      @provider.load_current_resource
-      expect(@provider.current_resource.package_name).to eq("f.o.o-pkg++2")
+    describe "when the package name has `-', `+' or `.' characters" do
+      let(:package) { "f.o.o-pkg++2" }
+
+      it "gets the source package name from dpkg-deb correctly" do
+        provider.load_current_resource
+        expect(provider.current_resource.package_name).to eq(["f.o.o-pkg++2"])
+      end
+    end
+
+    describe "when the package version has `~', `-', `+' or `.' characters" do
+      let(:package) { "b.a.r-pkg++1" }
+      let(:dpkg_deb_version) { "1.2.3+3141592-1ubuntu1~lucid" }
+      let(:dpkg_s_version) { "1.2.3+3141592-1ubuntu1~lucid" }
+
+      it "gets the source package version from dpkg-deb correctly when the package version has `~', `-', `+' or `.' characters" do
+        provider.load_current_resource
+        expect(provider.candidate_version).to eq(['1.2.3+3141592-1ubuntu1~lucid'])
+      end
     end
 
-    it "should raise an exception if the source is not set but we are installing" do
-      @new_resource = Chef::Resource::Package.new("wget")
-      @provider.new_resource = @new_resource
-      @provider.load_current_resource
-      @provider.define_resource_requirements
-      expect { @provider.run_action(:install)}.to raise_error(Chef::Exceptions::Package)
+    describe "when the source is not set" do
+      let(:source) { nil }
+
+      it "should raise an exception if the source is not set but we are installing" do
+        expect { provider.run_action(:install)}.to raise_error(Chef::Exceptions::Package)
+      end
     end
 
     it "should return the current version installed if found by dpkg" do
-      stdout = <<-DPKG_S
-Package: wget
-Status: install ok installed
-Priority: important
-Section: web
-Installed-Size: 1944
-Maintainer: Ubuntu Core developers <ubuntu-devel-discuss at lists.ubuntu.com>
-Architecture: amd64
-Version: 1.11.4-1ubuntu1
-Config-Version: 1.11.4-1ubuntu1
-Depends: libc6 (>= 2.8~20080505), libssl0.9.8 (>= 0.9.8f-5)
-Conflicts: wget-ssl
-DPKG_S
-      status = double(:stdout => stdout, :exitstatus => 1)
-      allow(@provider).to receive(:shell_out).with("dpkg -s wget").and_return(status)
+      allow(provider).to receive(:shell_out!).with("dpkg -s #{package}", timeout: 900, returns: [0,1]).and_return(dpkg_s_status)
+      provider.load_current_resource
+      expect(provider.current_resource.version).to eq(["1.11.4-1ubuntu1"])
+    end
 
-      @provider.load_current_resource
-      expect(@provider.current_resource.version).to eq("1.11.4-1ubuntu1")
+    it "on new debian/ubuntu we get an exit(1) and no stdout from dpkg -s for uninstalled" do
+      dpkg_s_status = double(
+        exitstatus: 1, stdout: "", stderr: <<-EOF
+dpkg-query: package '#{package}' is not installed and no information is available
+Use dpkg --info (= dpkg-deb --info) to examine archive files,
+and dpkg --contents (= dpkg-deb --contents) to list their contents.
+        EOF
+      )
+      expect(provider).to receive(:shell_out!).with("dpkg -s #{package}", returns: [0, 1], timeout: 900).and_return(dpkg_s_status)
+      provider.load_current_resource
+      expect(provider.current_resource.version).to eq([nil])
     end
 
-    it "should raise an exception if dpkg fails to run" do
+    it "on old debian/ubuntu we get an exit(0) and we get info on stdout from dpkg -s for uninstalled" do
+      dpkg_s_status = double(
+        exitstatus: 0, stderr: "", stdout: <<-EOF
+Package: #{package}
+Status: unknown ok not-installed
+Priority: extra
+Section: ruby
+        EOF
+      )
+      expect(provider).to receive(:shell_out!).with("dpkg -s #{package}", returns: [0, 1], timeout: 900).and_return(dpkg_s_status)
+      provider.load_current_resource
+      expect(provider.current_resource.version).to eq([nil])
+    end
+
+    it "and we should raise if we get any other exit codes from dpkg -s" do
+      dpkg_s_status = double(
+        exitstatus: 3, stderr: "i am very, very angry with you.  i'm very, very cross.  go to your room.", stdout: ""
+      )
+      expect(provider).to receive(:shell_out!).with("dpkg -s #{package}", returns: [0, 1], timeout: 900).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      expect { provider.load_current_resource }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
+    end
+
+    it "should raise an exception if dpkg-deb -W fails to run" do
       status = double(:stdout => "", :exitstatus => -1)
-      allow(@provider).to receive(:shell_out).and_return(status)
-      expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package)
+      expect(provider).to receive(:shell_out_with_timeout!).with("dpkg-deb -W /tmp/wget_1.11.4-1ubuntu1_amd64.deb").and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      expect { provider.load_current_resource }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
     end
   end
 
   describe Chef::Provider::Package::Dpkg, "install and upgrade" do
     it "should run dpkg -i with the package source" do
-      expect(@provider).to receive(:run_noninteractive).with(
-        "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb"
+      expect(provider).to receive(:run_noninteractive).with(
+        "dpkg -i", nil, "/tmp/wget_1.11.4-1ubuntu1_amd64.deb"
       )
-      @provider.install_package("wget", "1.11.4-1ubuntu1")
+      provider.load_current_resource
+      provider.run_action(:install)
     end
 
     it "should run dpkg -i if the package is a path and the source is nil" do
-      @new_resource = Chef::Resource::Package.new("/tmp/wget_1.11.4-1ubuntu1_amd64.deb")
-      @provider = Chef::Provider::Package::Dpkg.new(@new_resource, @run_context)
-      expect(@provider).to receive(:run_noninteractive).with(
-        "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb"
+      new_resource.name "/tmp/wget_1.11.4-1ubuntu1_amd64.deb"
+      expect(provider).to receive(:run_noninteractive).with(
+        "dpkg -i", nil, "/tmp/wget_1.11.4-1ubuntu1_amd64.deb"
       )
-      @provider.install_package("/tmp/wget_1.11.4-1ubuntu1_amd64.deb", "1.11.4-1ubuntu1")
+      provider.run_action(:install)
     end
 
     it "should run dpkg -i if the package is a path and the source is nil for an upgrade" do
-      @new_resource = Chef::Resource::Package.new("/tmp/wget_1.11.4-1ubuntu1_amd64.deb")
-      @provider = Chef::Provider::Package::Dpkg.new(@new_resource, @run_context)
-      expect(@provider).to receive(:run_noninteractive).with(
-        "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb"
+      new_resource.name "/tmp/wget_1.11.4-1ubuntu1_amd64.deb"
+      expect(provider).to receive(:run_noninteractive).with(
+        "dpkg -i", nil, "/tmp/wget_1.11.4-1ubuntu1_amd64.deb"
       )
-      @provider.upgrade_package("/tmp/wget_1.11.4-1ubuntu1_amd64.deb", "1.11.4-1ubuntu1")
+      provider.run_action(:upgrade)
     end
 
     it "should run dpkg -i with the package source and options if specified" do
-      expect(@provider).to receive(:run_noninteractive).with(
-        "dpkg -i --force-yes /tmp/wget_1.11.4-1ubuntu1_amd64.deb"
+      new_resource.options "--force-yes"
+      expect(provider).to receive(:run_noninteractive).with(
+        "dpkg -i", "--force-yes", "/tmp/wget_1.11.4-1ubuntu1_amd64.deb"
       )
-      allow(@new_resource).to receive(:options).and_return("--force-yes")
-
-      @provider.install_package("wget", "1.11.4-1ubuntu1")
+      provider.run_action(:install)
     end
+
     it "should upgrade by running install_package" do
-      expect(@provider).to receive(:install_package).with("wget", "1.11.4-1ubuntu1")
-      @provider.upgrade_package("wget", "1.11.4-1ubuntu1")
+      expect(provider).to receive(:install_package).with(["wget"], ["1.11.4-1ubuntu1"])
+      provider.upgrade_package(["wget"], ["1.11.4-1ubuntu1"])
     end
   end
 
   describe Chef::Provider::Package::Dpkg, "remove and purge" do
     it "should run dpkg -r to remove the package" do
-      expect(@provider).to receive(:run_noninteractive).with(
-        "dpkg -r wget"
+      expect(provider).to receive(:run_noninteractive).with(
+        "dpkg -r", nil, "wget"
       )
-      @provider.remove_package("wget", "1.11.4-1ubuntu1")
+      provider.remove_package(["wget"], ["1.11.4-1ubuntu1"])
     end
 
     it "should run dpkg -r to remove the package with options if specified" do
-      expect(@provider).to receive(:run_noninteractive).with(
-        "dpkg -r --force-yes wget"
+      expect(provider).to receive(:run_noninteractive).with(
+        "dpkg -r", "--force-yes", "wget"
       )
-      allow(@new_resource).to receive(:options).and_return("--force-yes")
+      allow(new_resource).to receive(:options).and_return("--force-yes")
 
-      @provider.remove_package("wget", "1.11.4-1ubuntu1")
+      provider.remove_package(["wget"], ["1.11.4-1ubuntu1"])
     end
 
     it "should run dpkg -P to purge the package" do
-      expect(@provider).to receive(:run_noninteractive).with(
-        "dpkg -P wget"
+      expect(provider).to receive(:run_noninteractive).with(
+        "dpkg -P", nil, "wget"
       )
-      @provider.purge_package("wget", "1.11.4-1ubuntu1")
+      provider.purge_package(["wget"], ["1.11.4-1ubuntu1"])
     end
 
     it "should run dpkg -P to purge the package with options if specified" do
-      expect(@provider).to receive(:run_noninteractive).with(
-        "dpkg -P --force-yes wget"
+      expect(provider).to receive(:run_noninteractive).with(
+        "dpkg -P", "--force-yes", "wget"
       )
-      allow(@new_resource).to receive(:options).and_return("--force-yes")
+      allow(new_resource).to receive(:options).and_return("--force-yes")
 
-      @provider.purge_package("wget", "1.11.4-1ubuntu1")
+      provider.purge_package(["wget"], ["1.11.4-1ubuntu1"])
     end
   end
 end
diff --git a/spec/unit/provider/package/freebsd/pkg_spec.rb b/spec/unit/provider/package/freebsd/pkg_spec.rb
index f671619..d1f5a64 100644
--- a/spec/unit/provider/package/freebsd/pkg_spec.rb
+++ b/spec/unit/provider/package/freebsd/pkg_spec.rb
@@ -77,7 +77,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
 
     it "should return the version number when it is installed" do
       pkg_info = OpenStruct.new(:stdout => "zsh-4.3.6_7")
-      expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+      expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0,1], timeout: 900).and_return(pkg_info)
       #@provider.should_receive(:popen4).with('pkg_info -E "zsh*"').and_yield(@pid, @stdin, ["zsh-4.3.6_7"], @stderr).and_return(@status)
       allow(@provider).to receive(:package_name).and_return("zsh")
       expect(@provider.current_installed_version).to eq("4.3.6_7")
@@ -85,14 +85,14 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
 
     it "does not set the current version number when the package is not installed" do
       pkg_info = OpenStruct.new(:stdout => "")
-      expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+      expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0,1], timeout: 900).and_return(pkg_info)
       allow(@provider).to receive(:package_name).and_return("zsh")
       expect(@provider.current_installed_version).to be_nil
     end
 
     it "should return the port path for a valid port name" do
       whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh")
-      expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis)
+      expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis)
       #@provider.should_receive(:popen4).with("whereis -s zsh").and_yield(@pid, @stdin, ["zsh: /usr/ports/shells/zsh"], @stderr).and_return(@status)
       allow(@provider).to receive(:port_name).and_return("zsh")
       expect(@provider.port_path).to eq("/usr/ports/shells/zsh")
@@ -102,7 +102,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
     it "should return the ports candidate version when given a valid port path" do
       allow(@provider).to receive(:port_path).and_return("/usr/ports/shells/zsh")
       make_v = OpenStruct.new(:stdout => "4.3.6\n", :exitstatus => 0)
-      expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", {:cwd=>"/usr/ports/shells/zsh", :returns=>[0, 1], :env=>nil}).and_return(make_v)
+      expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", {cwd: "/usr/ports/shells/zsh", returns: [0, 1], env: nil, timeout: 900}).and_return(make_v)
       expect(@provider.ports_candidate_version).to eq("4.3.6")
     end
 
@@ -110,7 +110,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
       allow(::File).to receive(:exist?).with('/usr/ports/Makefile').and_return(true)
       allow(@provider).to receive(:port_path).and_return("/usr/ports/shells/zsh")
       make_v = OpenStruct.new(:stdout => "zsh-4.3.6_7\n", :exitstatus => 0)
-      expect(@provider).to receive(:shell_out!).with("make -V PKGNAME", {:cwd=>"/usr/ports/shells/zsh", :env=>nil, :returns=>[0, 1]}).and_return(make_v)
+      expect(@provider).to receive(:shell_out!).with("make -V PKGNAME", {cwd: "/usr/ports/shells/zsh", env: nil, returns: [0, 1], timeout: 900}).and_return(make_v)
       #@provider.should_receive(:ports_makefile_variable_value).with("PKGNAME").and_return("zsh-4.3.6_7")
       expect(@provider.package_name).to eq("zsh")
     end
@@ -127,7 +127,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
     end
 
     it "should run pkg_add -r with the package name" do
-      expect(@provider).to receive(:shell_out!).with("pkg_add -r zsh", :env => nil).and_return(@cmd_result)
+      expect(@provider).to receive(:shell_out!).with("pkg_add -r zsh", env: nil, timeout: 900).and_return(@cmd_result)
       @provider.install_package("zsh", "4.3.6_7")
     end
   end
@@ -142,7 +142,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
 
     it "should figure out the port path from the package_name using whereis" do
       whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh")
-      expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env=>nil).and_return(whereis)
+      expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis)
       expect(@provider.port_path).to eq("/usr/ports/shells/zsh")
     end
 
@@ -178,7 +178,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
     end
 
     it "should run pkg_add -r with the package name" do
-      expect(@provider).to receive(:shell_out!).with("pkg_add -r ruby18-iconv", :env => nil).and_return(@install_result)
+      expect(@provider).to receive(:shell_out!).with("pkg_add -r ruby18-iconv", env: nil, timeout: 900).and_return(@install_result)
       @provider.install_package("ruby-iconv", "1.0")
     end
   end
@@ -193,7 +193,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
     end
 
     it "should run pkg_delete with the package name and version" do
-      expect(@provider).to receive(:shell_out!).with("pkg_delete zsh-4.3.6_7", :env => nil).and_return(@pkg_delete)
+      expect(@provider).to receive(:shell_out!).with("pkg_delete zsh-4.3.6_7", env: nil, timeout: 900).and_return(@pkg_delete)
       @provider.remove_package("zsh", "4.3.6_7")
     end
   end
@@ -213,14 +213,14 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
 
     it "should return the port path for a valid port name" do
       whereis = OpenStruct.new(:stdout => "bonnie++: /usr/ports/benchmarks/bonnie++")
-      expect(@provider).to receive(:shell_out!).with("whereis -s bonnie++", :env => nil).and_return(whereis)
+      expect(@provider).to receive(:shell_out!).with("whereis -s bonnie++", env: nil, timeout: 900).and_return(whereis)
       allow(@provider).to receive(:port_name).and_return("bonnie++")
       expect(@provider.port_path).to eq("/usr/ports/benchmarks/bonnie++")
     end
 
     it "should return the version number when it is installed" do
       pkg_info = OpenStruct.new(:stdout => "bonnie++-1.96")
-      expect(@provider).to receive(:shell_out!).with('pkg_info -E "bonnie++*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+      expect(@provider).to receive(:shell_out!).with('pkg_info -E "bonnie++*"', env: nil, returns: [0,1], timeout: 900).and_return(pkg_info)
       allow(@provider).to receive(:package_name).and_return("bonnie++")
       expect(@provider.current_installed_version).to eq("1.96")
     end
@@ -253,7 +253,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
       allow(@provider).to receive(:latest_link_name).and_return("perl")
 
       cmd = OpenStruct.new(:status => true)
-      expect(@provider).to receive(:shell_out!).with("pkg_add -r perl", :env => nil).and_return(cmd)
+      expect(@provider).to receive(:shell_out!).with("pkg_add -r perl", env: nil, timeout: 900).and_return(cmd)
       @provider.install_package("perl5.8", "5.8.8_1")
     end
 
@@ -267,7 +267,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
       allow(@provider).to receive(:latest_link_name).and_return("mysql50-server")
 
       cmd = OpenStruct.new(:status => true)
-      expect(@provider).to receive(:shell_out!).with("pkg_add -r mysql50-server", :env=>nil).and_return(cmd)
+      expect(@provider).to receive(:shell_out!).with("pkg_add -r mysql50-server", env: nil, timeout: 900).and_return(cmd)
       @provider.install_package("mysql50-server", "5.0.45_1")
     end
   end
diff --git a/spec/unit/provider/package/freebsd/pkgng_spec.rb b/spec/unit/provider/package/freebsd/pkgng_spec.rb
index 0c1e89c..59215f8 100644
--- a/spec/unit/provider/package/freebsd/pkgng_spec.rb
+++ b/spec/unit/provider/package/freebsd/pkgng_spec.rb
@@ -71,7 +71,7 @@ describe Chef::Provider::Package::Freebsd::Port do
     end
 
     it "should query pkg database" do
-      expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info)
+      expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0,70], timeout: 900).and_return(@pkg_info)
       expect(@provider.current_installed_version).to eq("3.1.7")
     end
   end
@@ -80,14 +80,14 @@ describe Chef::Provider::Package::Freebsd::Port do
   describe "determining candidate version" do
     it "should query repository" do
       pkg_query = OpenStruct.new(:stdout => "5.0.5\n", :exitstatus => 0)
-      expect(@provider).to receive(:shell_out!).with("pkg rquery '%v' zsh", :env => nil).and_return(pkg_query)
+      expect(@provider).to receive(:shell_out!).with("pkg rquery '%v' zsh", env: nil, timeout: 900).and_return(pkg_query)
       expect(@provider.candidate_version).to eq("5.0.5")
     end
 
     it "should query specified repository when given option" do
       @provider.new_resource.options('-r LocalMirror') # This requires LocalMirror repo configuration.
       pkg_query = OpenStruct.new(:stdout => "5.0.3\n", :exitstatus => 0)
-      expect(@provider).to receive(:shell_out!).with("pkg rquery -r LocalMirror '%v' zsh", :env => nil).and_return(pkg_query)
+      expect(@provider).to receive(:shell_out!).with("pkg rquery -r LocalMirror '%v' zsh", env: nil, timeout: 900).and_return(pkg_query)
       expect(@provider.candidate_version).to eq("5.0.3")
     end
 
@@ -106,7 +106,7 @@ describe Chef::Provider::Package::Freebsd::Port do
     it "should handle package source from file" do
       @provider.new_resource.source("/nas/pkg/repo/zsh-5.0.1.txz")
       expect(@provider).to receive(:shell_out!).
-        with("pkg add /nas/pkg/repo/zsh-5.0.1.txz", :env => { 'LC_ALL' => nil }).
+        with("pkg add /nas/pkg/repo/zsh-5.0.1.txz", env: { 'LC_ALL' => nil }, timeout: 900).
         and_return(@install_result)
       @provider.install_package("zsh", "5.0.1")
     end
@@ -114,21 +114,21 @@ describe Chef::Provider::Package::Freebsd::Port do
     it "should handle package source over ftp or http" do
       @provider.new_resource.source("http://repo.example.com/zsh-5.0.1.txz")
       expect(@provider).to receive(:shell_out!).
-        with("pkg add http://repo.example.com/zsh-5.0.1.txz", :env => { 'LC_ALL' => nil }).
+        with("pkg add http://repo.example.com/zsh-5.0.1.txz", env: { 'LC_ALL' => nil }, timeout: 900).
         and_return(@install_result)
       @provider.install_package("zsh", "5.0.1")
     end
 
     it "should handle a package name" do
       expect(@provider).to receive(:shell_out!).
-        with("pkg install -y zsh", :env => { 'LC_ALL' => nil }).and_return(@install_result)
+        with("pkg install -y zsh", env: { 'LC_ALL' => nil }, timeout: 900).and_return(@install_result)
       @provider.install_package("zsh", "5.0.1")
     end
 
     it "should handle a package name with a specified repo" do
       @provider.new_resource.options('-r LocalMirror') # This requires LocalMirror repo configuration.
       expect(@provider).to receive(:shell_out!).
-        with("pkg install -y -r LocalMirror zsh", :env => { 'LC_ALL' => nil }).and_return(@install_result)
+        with("pkg install -y -r LocalMirror zsh", env: { 'LC_ALL' => nil }, timeout: 900).and_return(@install_result)
       @provider.install_package("zsh", "5.0.1")
     end
   end
@@ -141,14 +141,14 @@ describe Chef::Provider::Package::Freebsd::Port do
 
     it "should call pkg delete" do
       expect(@provider).to receive(:shell_out!).
-        with("pkg delete -y zsh-5.0.1", :env => nil).and_return(@install_result)
+        with("pkg delete -y zsh-5.0.1", env: nil, timeout: 900).and_return(@install_result)
       @provider.remove_package("zsh", "5.0.1")
     end
 
     it "should not include repo option in pkg delete" do
       @provider.new_resource.options('-r LocalMirror') # This requires LocalMirror repo configuration.
       expect(@provider).to receive(:shell_out!).
-        with("pkg delete -y zsh-5.0.1", :env => nil).and_return(@install_result)
+        with("pkg delete -y zsh-5.0.1", env: nil, timeout: 900).and_return(@install_result)
       @provider.remove_package("zsh", "5.0.1")
     end
   end
diff --git a/spec/unit/provider/package/freebsd/port_spec.rb b/spec/unit/provider/package/freebsd/port_spec.rb
index 2e32e88..4b23575 100644
--- a/spec/unit/provider/package/freebsd/port_spec.rb
+++ b/spec/unit/provider/package/freebsd/port_spec.rb
@@ -72,7 +72,7 @@ describe Chef::Provider::Package::Freebsd::Port do
     it "should check 'pkg_info' if system uses pkg_* tools" do
       allow(@new_resource).to receive(:supports_pkgng?)
       expect(@new_resource).to receive(:supports_pkgng?).and_return(false)
-      expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(@pkg_info)
+      expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0,1], timeout: 900).and_return(@pkg_info)
       expect(@provider.current_installed_version).to eq("3.1.7")
     end
 
@@ -80,8 +80,8 @@ describe Chef::Provider::Package::Freebsd::Port do
       pkg_enabled = OpenStruct.new(:stdout => "yes\n")
       [1000016, 1000000, 901503, 902506, 802511].each do |__freebsd_version|
         @node.automatic_attrs[:os_version] = __freebsd_version
-        expect(@new_resource).to receive(:shell_out!).with('make -V WITH_PKGNG', :env => nil).and_return(pkg_enabled)
-        expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info)
+        expect(@new_resource).to receive(:shell_out!).with('make -V WITH_PKGNG', env: nil).and_return(pkg_enabled)
+        expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0,70], timeout: 900).and_return(@pkg_info)
         expect(@provider.current_installed_version).to eq("3.1.7")
       end
     end
@@ -89,7 +89,7 @@ describe Chef::Provider::Package::Freebsd::Port do
     it "should check 'pkg info' if the freebsd version is greater than or equal to 1000017" do
       __freebsd_version = 1000017
       @node.automatic_attrs[:os_version] = __freebsd_version
-      expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info)
+      expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0,70], timeout: 900).and_return(@pkg_info)
       expect(@provider.current_installed_version).to eq("3.1.7")
     end
   end
@@ -102,7 +102,7 @@ describe Chef::Provider::Package::Freebsd::Port do
     it "should return candidate version if port exists" do
       allow(::File).to receive(:exist?).with('/usr/ports/Makefile').and_return(true)
       allow(@provider).to receive(:port_dir).and_return('/usr/ports/shells/zsh')
-      expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", :cwd => "/usr/ports/shells/zsh", :env => nil, :returns => [0,1]).
+      expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", cwd: "/usr/ports/shells/zsh", env: nil, returns: [0,1], timeout: 900).
         and_return(@port_version)
       expect(@provider.candidate_version).to eq("5.0.5")
     end
@@ -127,13 +127,13 @@ describe Chef::Provider::Package::Freebsd::Port do
 
     it "should query system for path given just a name" do
       whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh\n")
-      expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis)
+      expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis)
       expect(@provider.port_dir).to eq("/usr/ports/shells/zsh")
     end
 
     it "should raise exception if not found" do
       whereis = OpenStruct.new(:stdout => "zsh:\n")
-      expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis)
+      expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis)
       expect { @provider.port_dir }.to raise_error(Chef::Exceptions::Package, "Could not find port with the name zsh")
     end
   end
diff --git a/spec/unit/provider/package/ips_spec.rb b/spec/unit/provider/package/ips_spec.rb
index 342ac4c..ad69dff 100644
--- a/spec/unit/provider/package/ips_spec.rb
+++ b/spec/unit/provider/package/ips_spec.rb
@@ -65,28 +65,28 @@ PKG_STATUS
 
   context "when loading current resource" do
     it "should create a current resource with the name of the new_resource" do
-      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
-      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
       expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
       @provider.load_current_resource
     end
 
     it "should set the current resources package name to the new resources package name" do
-      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
-      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
       @provider.load_current_resource
       expect(@current_resource.package_name).to eq(@new_resource.package_name)
     end
 
     it "should run pkg info with the package name" do
-      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
-      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
       @provider.load_current_resource
     end
 
     it "should set the installed version to nil on the current resource if package state is not installed" do
-      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
-      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
       @provider.load_current_resource
       expect(@current_resource.version).to be_nil
     end
@@ -108,27 +108,27 @@ Packaging Date: October 19, 2011 09:14:50 AM
           Size: 8.07 MB
           FMRI: pkg://solaris/crypto/gnupg@2.0.17,5.11-0.175.0.0.0.2.537:20111019T091450Z
 INSTALLED
-      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local)
-      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local)
+      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
       @provider.load_current_resource
       expect(@current_resource.version).to eq("2.0.17")
     end
 
     it "should return the current resource" do
-      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
-      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
       expect(@provider.load_current_resource).to eql(@current_resource)
     end
   end
 
   context "when installing a package" do
     it "should run pkg install with the package name and version" do
-      expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg at 2.0.17")
+      expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg at 2.0.17", timeout: 900)
       @provider.install_package("crypto/gnupg", "2.0.17")
     end
 
     it "should run pkg install with the package name and version and options if specified" do
-      expect(@provider).to receive(:shell_out).with("pkg --no-refresh install -q crypto/gnupg at 2.0.17")
+      expect(@provider).to receive(:shell_out).with("pkg --no-refresh install -q crypto/gnupg at 2.0.17", timeout: 900)
       allow(@new_resource).to receive(:options).and_return("--no-refresh")
       @provider.install_package("crypto/gnupg", "2.0.17")
     end
@@ -147,8 +147,8 @@ Packaging Date: April  1, 2012 05:55:52 PM
           Size: 2.57 MB
           FMRI: pkg://omnios/security/sudo@1.8.4.1,5.11-0.151002:20120401T175552Z
 PKG_STATUS
-      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
-      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote)
+      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote)
       @provider.load_current_resource
       expect(@current_resource.version).to be_nil
       expect(@provider.candidate_version).to eql("1.8.4.1")
@@ -188,8 +188,8 @@ Packaging Date: October 19, 2011 09:14:50 AM
           FMRI: pkg://solaris/crypto/gnupg@2.0.18,5.11-0.175.0.0.0.2.537:20111019T091450Z
 REMOTE
 
-      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local)
-      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote)
+      expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local)
+      expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote)
       expect(@provider).to receive(:install_package).exactly(0).times
       @provider.run_action(:install)
     end
@@ -200,7 +200,7 @@ REMOTE
       end
 
       it "should run pkg install with the --accept flag" do
-        expect(@provider).to receive(:shell_out).with("pkg install -q --accept crypto/gnupg at 2.0.17")
+        expect(@provider).to receive(:shell_out).with("pkg install -q --accept crypto/gnupg at 2.0.17", timeout: 900)
         @provider.install_package("crypto/gnupg", "2.0.17")
       end
     end
@@ -208,19 +208,19 @@ REMOTE
 
   context "when upgrading a package" do
     it "should run pkg install with the package name and version" do
-      expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg at 2.0.17")
+      expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg at 2.0.17", timeout: 900)
       @provider.upgrade_package("crypto/gnupg", "2.0.17")
     end
   end
 
   context "when uninstalling a package" do
     it "should run pkg uninstall with the package name and version" do
-      expect(@provider).to receive(:shell_out!).with("pkg uninstall -q crypto/gnupg at 2.0.17")
+      expect(@provider).to receive(:shell_out!).with("pkg uninstall -q crypto/gnupg at 2.0.17", timeout: 900)
       @provider.remove_package("crypto/gnupg", "2.0.17")
     end
 
     it "should run pkg uninstall with the package name and version and options if specified" do
-      expect(@provider).to receive(:shell_out!).with("pkg --no-refresh uninstall -q crypto/gnupg at 2.0.17")
+      expect(@provider).to receive(:shell_out!).with("pkg --no-refresh uninstall -q crypto/gnupg at 2.0.17", timeout: 900)
       allow(@new_resource).to receive(:options).and_return("--no-refresh")
       @provider.remove_package("crypto/gnupg", "2.0.17")
     end
diff --git a/spec/unit/provider/package/macports_spec.rb b/spec/unit/provider/package/macports_spec.rb
index 9822fb3..eef8411 100644
--- a/spec/unit/provider/package/macports_spec.rb
+++ b/spec/unit/provider/package/macports_spec.rb
@@ -105,7 +105,7 @@ EOF
     it "should run the port install command with the correct version" do
       expect(@current_resource).to receive(:version).and_return("4.1.6")
       @provider.current_resource = @current_resource
-      expect(@provider).to receive(:shell_out!).with("port install zsh @4.2.7")
+      expect(@provider).to receive(:shell_out!).with("port install zsh @4.2.7", timeout: 900)
 
       @provider.install_package("zsh", "4.2.7")
     end
@@ -122,7 +122,7 @@ EOF
       expect(@current_resource).to receive(:version).and_return("4.1.6")
       @provider.current_resource = @current_resource
       allow(@new_resource).to receive(:options).and_return("-f")
-      expect(@provider).to receive(:shell_out!).with("port -f install zsh @4.2.7")
+      expect(@provider).to receive(:shell_out!).with("port -f install zsh @4.2.7", timeout: 900)
 
       @provider.install_package("zsh", "4.2.7")
     end
@@ -130,36 +130,36 @@ EOF
 
   describe "purge_package" do
     it "should run the port uninstall command with the correct version" do
-      expect(@provider).to receive(:shell_out!).with("port uninstall zsh @4.2.7")
+      expect(@provider).to receive(:shell_out!).with("port uninstall zsh @4.2.7", timeout: 900)
       @provider.purge_package("zsh", "4.2.7")
     end
 
     it "should purge the currently active version if no explicit version is passed in" do
-      expect(@provider).to receive(:shell_out!).with("port uninstall zsh")
+      expect(@provider).to receive(:shell_out!).with("port uninstall zsh", timeout: 900)
       @provider.purge_package("zsh", nil)
     end
 
     it "should add options to the port command when specified" do
       allow(@new_resource).to receive(:options).and_return("-f")
-      expect(@provider).to receive(:shell_out!).with("port -f uninstall zsh @4.2.7")
+      expect(@provider).to receive(:shell_out!).with("port -f uninstall zsh @4.2.7", timeout: 900)
       @provider.purge_package("zsh", "4.2.7")
     end
   end
 
   describe "remove_package" do
     it "should run the port deactivate command with the correct version" do
-      expect(@provider).to receive(:shell_out!).with("port deactivate zsh @4.2.7")
+      expect(@provider).to receive(:shell_out!).with("port deactivate zsh @4.2.7", timeout: 900)
       @provider.remove_package("zsh", "4.2.7")
     end
 
     it "should remove the currently active version if no explicit version is passed in" do
-      expect(@provider).to receive(:shell_out!).with("port deactivate zsh")
+      expect(@provider).to receive(:shell_out!).with("port deactivate zsh", timeout: 900)
       @provider.remove_package("zsh", nil)
     end
 
     it "should add options to the port command when specified" do
       allow(@new_resource).to receive(:options).and_return("-f")
-      expect(@provider).to receive(:shell_out!).with("port -f deactivate zsh @4.2.7")
+      expect(@provider).to receive(:shell_out!).with("port -f deactivate zsh @4.2.7", timeout: 900)
       @provider.remove_package("zsh", "4.2.7")
     end
   end
@@ -169,7 +169,7 @@ EOF
       expect(@current_resource).to receive(:version).at_least(:once).and_return("4.1.6")
       @provider.current_resource = @current_resource
 
-      expect(@provider).to receive(:shell_out!).with("port upgrade zsh @4.2.7")
+      expect(@provider).to receive(:shell_out!).with("port upgrade zsh @4.2.7", timeout: 900)
 
       @provider.upgrade_package("zsh", "4.2.7")
     end
@@ -195,7 +195,7 @@ EOF
       expect(@current_resource).to receive(:version).at_least(:once).and_return("4.1.6")
       @provider.current_resource = @current_resource
 
-      expect(@provider).to receive(:shell_out!).with("port -f upgrade zsh @4.2.7")
+      expect(@provider).to receive(:shell_out!).with("port -f upgrade zsh @4.2.7", timeout: 900)
 
       @provider.upgrade_package("zsh", "4.2.7")
     end
diff --git a/spec/unit/provider/package/openbsd_spec.rb b/spec/unit/provider/package/openbsd_spec.rb
index b0cdb99..8407f83 100644
--- a/spec/unit/provider/package/openbsd_spec.rb
+++ b/spec/unit/provider/package/openbsd_spec.rb
@@ -50,25 +50,13 @@ describe Chef::Provider::Package::Openbsd do
 
       context 'when there is a single candidate' do
 
-        context 'when installing from source' do
-          it 'should run the installation command' do
-            pending('Installing from source is not supported yet')
-            # This is a consequence of load_current_resource being called before define_resource_requirements
-            # It can be deleted once an implementation is provided
-            allow(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return(
-              instance_double('shellout', :stdout => "#{name}-#{version}\n"))
-            new_resource.source('/some/path/on/disk.tgz')
-            provider.run_action(:install)
-          end
-        end
-
         context 'when source is not provided' do
           it 'should run the installation command' do
             expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return(
               instance_double('shellout', :stdout => "#{name}-#{version}\n"))
             expect(provider).to receive(:shell_out!).with(
               "pkg_add -r #{name}-#{version}",
-              {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}}
+              {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}, timeout: 900}
             ) {OpenStruct.new :status => true}
             provider.run_action(:install)
           end
@@ -100,21 +88,12 @@ describe Chef::Provider::Package::Openbsd do
                 instance_double('shellout', :stdout => "#{name}-#{version}-#{flavor}\n"))
               expect(provider).to receive(:shell_out!).with(
                 "pkg_add -r #{name}-#{version}-#{flavor}",
-                {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}}
+                {env: {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}, timeout: 900}
               ) {OpenStruct.new :status => true}
               provider.run_action(:install)
             end
           end
 
-          context 'if a version is specified' do
-            it 'runs the installation command' do
-              pending('Specifying both a version and flavor is not supported')
-              new_resource.version(version)
-              allow(provider).to receive(:shell_out!).with(/pkg_info -e/, anything()).and_return(instance_double('shellout', :stdout => ''))
-              allow(provider).to receive(:candidate_version).and_return("#{package_name}-#{version}-#{flavor}")
-              provider.run_action(:install)
-            end
-          end
         end
 
         context 'if a version is specified' do
@@ -125,7 +104,7 @@ describe Chef::Provider::Package::Openbsd do
             new_resource.version("#{version}-#{flavor_b}")
             expect(provider).to receive(:shell_out!).with(
               "pkg_add -r #{name}-#{version}-#{flavor_b}",
-              {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}}
+              {env: {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}, timeout: 900}
             ) {OpenStruct.new :status => true}
             provider.run_action(:install)
           end
@@ -144,11 +123,10 @@ describe Chef::Provider::Package::Openbsd do
     end
     it "should run the command to delete the installed package" do
       expect(@provider).to receive(:shell_out!).with(
-        "pkg_delete #{@name}", :env=>nil
+        "pkg_delete #{@name}", env: nil, timeout: 900
       ) {OpenStruct.new :status => true}
       @provider.remove_package(@name, nil)
     end
   end
 
 end
-
diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb
index 3b8848c..fcb9f8a 100644
--- a/spec/unit/provider/package/pacman_spec.rb
+++ b/spec/unit/provider/package/pacman_spec.rb
@@ -51,7 +51,7 @@ ERR
     end
 
     it "should run pacman query with the package name" do
-      expect(@provider).to receive(:shell_out).with("pacman -Qi #{@new_resource.package_name}").and_return(@status)
+      expect(@provider).to receive(:shell_out).with("pacman -Qi #{@new_resource.package_name}", {timeout: 900}).and_return(@status)
       @provider.load_current_resource
     end
 
@@ -152,12 +152,12 @@ PACMAN_CONF
 
   describe Chef::Provider::Package::Pacman, "install_package" do
     it "should run pacman install with the package name and version" do
-      expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar nano")
+      expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar nano", {timeout: 900})
       @provider.install_package("nano", "1.0")
     end
 
     it "should run pacman install with the package name and version and options if specified" do
-      expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar --debug nano")
+      expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar --debug nano", {timeout: 900})
       allow(@new_resource).to receive(:options).and_return("--debug")
 
       @provider.install_package("nano", "1.0")
@@ -173,12 +173,12 @@ PACMAN_CONF
 
   describe Chef::Provider::Package::Pacman, "remove_package" do
     it "should run pacman remove with the package name" do
-      expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar nano")
+      expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar nano", {timeout: 900})
       @provider.remove_package("nano", "1.0")
     end
 
     it "should run pacman remove with the package name and options if specified" do
-      expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar --debug nano")
+      expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar --debug nano", {timeout: 900})
       allow(@new_resource).to receive(:options).and_return("--debug")
 
       @provider.remove_package("nano", "1.0")
diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb
index 411afd3..ad9d694 100644
--- a/spec/unit/provider/package/rpm_spec.rb
+++ b/spec/unit/provider/package/rpm_spec.rb
@@ -23,183 +23,411 @@ describe Chef::Provider::Package::Rpm do
   let(:node) { Chef::Node.new }
   let(:events) { Chef::EventDispatch::Dispatcher.new }
   let(:run_context) { Chef::RunContext.new(node, {}, events) }
+
+  let(:package_source) { "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" }
+
+  let(:package_name) { "ImageMagick-c++" }
+
   let(:new_resource) do
-    Chef::Resource::Package.new("ImageMagick-c++").tap do |resource|
-      resource.source "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
+    Chef::Resource::Package.new(package_name).tap do |resource|
+      resource.source(package_source)
     end
   end
-  let(:exitstatus) { 0 }
-  let(:stdout) { String.new('') }
-  let(:status) { double('Process::Status', exitstatus: exitstatus, stdout: stdout) }
+
+  # `rpm -qp [stuff] $source`
+  let(:rpm_qp_status) { instance_double('Mixlib::ShellOut', exitstatus: rpm_qp_exitstatus, stdout: rpm_qp_stdout) }
+
+  # `rpm -q [stuff] $package_name`
+  let(:rpm_q_status) { instance_double('Mixlib::ShellOut', exitstatus: rpm_q_exitstatus, stdout: rpm_q_stdout) }
 
   before(:each) do
-    allow(::File).to receive(:exists?).and_return(true)
-    allow(provider).to receive(:shell_out!).and_return(status)
+    allow(::File).to receive(:exists?).with("PLEASE STUB File.exists? EXACTLY").and_return(true)
+
+    # Ensure all shell out usage is stubbed with exact arguments
+    allow(provider).to receive(:shell_out!).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil)
+    allow(provider).to receive(:shell_out).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil)
   end
 
-  describe "when determining the current state of the package" do
-    it "should create a current resource with the name of new_resource" do
-      provider.load_current_resource
-      expect(provider.current_resource.name).to eq("ImageMagick-c++")
-    end
+  describe "when the package source is not valid" do
 
-    it "should set the current reource package name to the new resource package name" do
-      provider.load_current_resource
-      expect(provider.current_resource.package_name).to eq('ImageMagick-c++')
-    end
+    context "when source is not defiend" do
+      let(:new_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
 
-    it "should raise an exception if a source is supplied but not found" do
-      allow(::File).to receive(:exists?).and_return(false)
-      expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
+      it "should raise an exception when attempting any action" do
+        expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
+      end
     end
 
-    context "installation exists" do
-      let(:stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+    context "when the source is a file that doesn't exist" do
 
-      it "should get the source package version from rpm if provided" do
-        expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm").and_return(status)
-        expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' ImageMagick-c++").and_return(status)
-        provider.load_current_resource
-        expect(provider.current_resource.package_name).to eq("ImageMagick-c++")
-        expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5")
+      it "should raise an exception when attempting any action" do
+        allow(::File).to receive(:exists?).with(package_source).and_return(false)
+        expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
       end
+    end
 
-      it "should return the current version installed if found by rpm" do
-        expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm").and_return(status)
-        expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' ImageMagick-c++").and_return(status)
-        provider.load_current_resource
-        expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5")
+    context "when the source is an unsupported URI scheme" do
+
+      let(:package_source) { "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" }
+
+      it "should raise an exception if an uri formed source is non-supported scheme" do
+        allow(::File).to receive(:exists?).with(package_source).and_return(false)
+
+        # verify let bindings are as we expect
+        expect(new_resource.source).to eq("foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+        expect(provider.load_current_resource).to be_nil
+        expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
       end
     end
 
-    context "source is uri formed" do
-      before(:each) do
-        allow(::File).to receive(:exists?).and_return(false)
+  end
+
+  describe "when the package source is valid" do
+
+    before do
+      expect(provider).to receive(:shell_out!).
+        with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_source}", timeout: 900).
+        and_return(rpm_qp_status)
+
+      expect(provider).to receive(:shell_out).
+        with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_name}", timeout: 900).
+        and_return(rpm_q_status)
+    end
+
+    context "when rpm fails when querying package installed state" do
+
+      before do
+        allow(::File).to receive(:exists?).with(package_source).and_return(true)
       end
 
-      %w(http HTTP https HTTPS ftp FTP).each do |scheme|
-        it "should accept uri formed source (#{scheme})" do
-          new_resource.source "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
-          expect(provider.load_current_resource).not_to be_nil
+      let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+      let(:rpm_q_stdout) { "" }
+
+      let(:rpm_qp_exitstatus) { 0 }
+      let(:rpm_q_exitstatus) { -1 }
+
+      it "raises an exception when attempting any action" do
+        expected_message = "Unable to determine current version due to RPM failure."
+
+        expect { provider.run_action(:install) }.to raise_error do |error|
+          expect(error).to be_a_kind_of(Chef::Exceptions::Package)
+          expect(error.to_s).to include(expected_message)
         end
       end
+    end
+
+
+    context "when the package is installed" do
+
+      let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+      let(:rpm_q_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+
+      let(:rpm_qp_exitstatus) { 0 }
+      let(:rpm_q_exitstatus) { 0 }
+
+      let(:action) { :install }
+
+      context "when the source is a file system path" do
 
-      %w(file FILE).each do |scheme|
-        it "should accept uri formed source (#{scheme})" do
-          new_resource.source "#{scheme}:///ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
-          expect(provider.load_current_resource).not_to be_nil
+        before do
+          allow(::File).to receive(:exists?).with(package_source).and_return(true)
+
+          provider.action = action
+
+          provider.load_current_resource
+          provider.define_resource_requirements
+          provider.process_resource_requirements
         end
-      end
 
-      it "should raise an exception if an uri formed source is non-supported scheme" do
-        new_resource.source "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
-        expect(provider.load_current_resource).to be_nil
-        expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
-      end
-    end
+        it "should get the source package version from rpm if provided" do
+          expect(provider.current_resource.package_name).to eq("ImageMagick-c++")
+          expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5")
+        end
 
-    context "source is not defiend" do
-      let(:new_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
+        it "should return the current version installed if found by rpm" do
+          expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5")
+        end
+
+        describe "action install" do
+
+          context "when at the desired version already" do
+            it "does nothing when the correct version is installed" do
+              expect(provider).to_not receive(:shell_out!).with("rpm  -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+
+              provider.action_install
+            end
+          end
+
+          context "when a newer version is desired" do
+
+            let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" }
+
+            it "runs rpm -u with the package source to upgrade" do
+              expect(provider).to receive(:shell_out!).with("rpm  -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+              provider.action_install
+            end
+          end
+
+          context "when an older version is desired" do
+            let(:new_resource) do
+              Chef::Resource::RpmPackage.new(package_name).tap do |r|
+                r.source(package_source)
+                r.allow_downgrade(true)
+              end
+            end
+
+            let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" }
+
+            it "should run rpm -u --oldpackage with the package source to downgrade" do
+              expect(provider).to receive(:shell_out!).with("rpm  -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+              provider.action_install
+            end
+
+          end
+
+        end
+
+        describe "action upgrade" do
+
+          let(:action) { :upgrade }
+
+          context "when at the desired version already" do
+            it "does nothing when the correct version is installed" do
+              expect(provider).to_not receive(:shell_out!).with("rpm  -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+
+              provider.action_upgrade
+            end
+          end
+
+          context "when a newer version is desired" do
+
+            let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" }
+
+            it "runs rpm -u with the package source to upgrade" do
+              expect(provider).to receive(:shell_out!).with("rpm  -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+              provider.action_upgrade
+            end
+          end
+
+          context "when an older version is desired" do
+            let(:new_resource) do
+              Chef::Resource::RpmPackage.new(package_name).tap do |r|
+                r.source(package_source)
+                r.allow_downgrade(true)
+              end
+            end
+
+            let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" }
+
+            it "should run rpm -u --oldpackage with the package source to downgrade" do
+              expect(provider).to receive(:shell_out!).with("rpm  -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+              provider.action_upgrade
+            end
+
+          end
+        end
+
+        describe "action :remove" do
+
+          let(:action) { :remove }
+
+          it "should remove the package" do
+            expect(provider).to receive(:shell_out!).with("rpm  -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900)
+            provider.action_remove
+          end
+        end
+
+
+        context "when the package name contains a tilde (chef#3503)" do
+
+          let(:package_name) { "supermarket" }
+
+          let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" }
+
+          let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" }
+          let(:rpm_q_stdout) { "supermarket 1.10.1~alpha.0-1.el5" }
+
+          let(:rpm_qp_exitstatus) { 0 }
+          let(:rpm_q_exitstatus) { 0 }
+
+          it "should correctly determine the candidate version and installed version" do
+            expect(provider.current_resource.package_name).to eq("supermarket")
+            expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5")
+          end
+        end
+
+        context "when the package name contains a plus symbol (chef#3671)" do
+
+          let(:package_name) { "chef-server-core" }
+
+          let(:package_source) { "/tmp/chef-server-core-12.2.0+20150713220422-1.el6.x86_64.rpm" }
+
+          let(:rpm_qp_stdout) { "chef-server-core 12.2.0+20150713220422-1.el6" }
+          let(:rpm_q_stdout) { "chef-server-core 12.2.0+20150713220422-1.el6" }
+
+          let(:rpm_qp_exitstatus) { 0 }
+          let(:rpm_q_exitstatus) { 0 }
+
+          it "should correctly determine the candidate version and installed version" do
+            expect(provider.current_resource.package_name).to eq("chef-server-core")
+            expect(provider.new_resource.version).to eq("12.2.0+20150713220422-1.el6")
+          end
+        end
 
-      it "should raise an exception if the source is not set but we are installing" do
-        expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
       end
-    end
 
-    context "installation does not exist" do
-      let(:stdout) { String.new("package openssh-askpass is not installed") }
-      let(:exitstatus) { -1 }
-      let(:new_resource) do
-        Chef::Resource::Package.new("openssh-askpass").tap do |resource|
-          resource.source "openssh-askpass"
+      context "when the source is given as an URI" do
+        before(:each) do
+          allow(::File).to receive(:exists?).with(package_source).and_return(false)
+
+          provider.action = action
+
+          provider.load_current_resource
+          provider.define_resource_requirements
+          provider.process_resource_requirements
+        end
+
+        %w(http HTTP https HTTPS ftp FTP file FILE).each do |scheme|
+
+          context "when the source URI uses protocol scheme '#{scheme}'" do
+
+            let(:package_source) { "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" }
+
+            it "should get the source package version from rpm if provided" do
+              expect(provider.current_resource.package_name).to eq("ImageMagick-c++")
+              expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5")
+            end
+
+            it "should return the current version installed if found by rpm" do
+              expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5")
+            end
+
+          end
         end
+
       end
 
-      it "should raise an exception if rpm fails to run" do
-        allow(provider).to receive(:shell_out).and_return(status)
-        expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
+    end
+
+    context "when the package is not installed" do
+
+      let(:package_name) { "openssh-askpass" }
+
+      let(:package_source) { "/tmp/openssh-askpass-1.2.3-4.el6_5.x86_64.rpm" }
+
+      let(:rpm_qp_stdout) { "openssh-askpass 1.2.3-4.el6_5" }
+      let(:rpm_q_stdout) { "package openssh-askpass is not installed" }
+
+      let(:rpm_qp_exitstatus) { 0 }
+      let(:rpm_q_exitstatus) { 0 }
+
+      let(:action) { :install }
+
+      before do
+        allow(File).to receive(:exists?).with(package_source).and_return(true)
+
+        provider.action = action
+
+        provider.load_current_resource
+        provider.define_resource_requirements
+        provider.process_resource_requirements
       end
 
       it "should not detect the package name as version when not installed" do
-        expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass").and_return(status)
-        expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass").and_return(status)
-        provider.load_current_resource
         expect(provider.current_resource.version).to be_nil
       end
-    end
-  end
 
-  describe "after the current resource is loaded" do
-    let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
-    let(:provider) do
-      Chef::Provider::Package::Rpm.new(new_resource, run_context).tap do |provider|
-        provider.current_resource = current_resource
-      end
-    end
+      context "when the package name contains a tilde (chef#3503)" do
 
-    describe "when installing or upgrading" do
-      it "should run rpm -i with the package source to install" do
-        expect(provider).to receive(:shell_out!).with("rpm  -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
-        provider.install_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
-      end
+        let(:package_name) { "supermarket" }
 
-      it "should run rpm -U with the package source to upgrade" do
-        current_resource.version("21.4-19.el5")
-        expect(provider).to receive(:shell_out!).with("rpm  -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
-        provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
-      end
+        let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" }
 
-      it "should install package if missing and set to upgrade" do
-        current_resource.version("ImageMagick-c++")
-        expect(provider).to receive(:shell_out!).with("rpm  -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
-        provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
-      end
+        let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" }
+        let(:rpm_q_stdout) { "package supermarket is not installed" }
 
-      context "allowing downgrade" do
-        let(:new_resource) { Chef::Resource::RpmPackage.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") }
-        let(:current_resource) { Chef::Resource::RpmPackage.new("ImageMagick-c++") }
+        let(:rpm_qp_exitstatus) { 0 }
+        let(:rpm_q_exitstatus) { 0 }
 
-        it "should run rpm -U --oldpackage with the package source to downgrade" do
-          new_resource.allow_downgrade(true)
-          current_resource.version("21.4-19.el5")
-          expect(provider).to receive(:shell_out!).with("rpm  -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
-          provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
+        it "should correctly determine the candidate version" do
+          expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5")
         end
       end
 
-      context "installing when the name is a path" do
-        let(:new_resource) { Chef::Resource::Package.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") }
-        let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
+      describe "managing the package" do
+
+        describe "action install" do
+
+          it "installs the package" do
+            expect(provider).to receive(:shell_out!).with("rpm  -i #{package_source}", timeout: 900)
 
-        it "should install from a path when the package is a path and the source is nil" do
-          expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
-          provider.current_resource = current_resource
-          expect(provider).to receive(:shell_out!).with("rpm  -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
-          provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
+            provider.action_install
+          end
+
+          context "when custom resource options are given" do
+            it "installs with custom options specified in the resource" do
+              new_resource.options("--dbpath /var/lib/rpm")
+              expect(provider).to receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i #{package_source}", timeout: 900)
+              provider.action_install
+            end
+          end
         end
 
-        it "should uprgrade from a path when the package is a path and the source is nil" do
-          expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
-          current_resource.version("21.4-19.el5")
-          provider.current_resource = current_resource
-          expect(provider).to receive(:shell_out!).with("rpm  -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
-          provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
+        describe "action upgrade" do
+
+          let(:action) { :upgrade }
+
+          it "installs the package" do
+            expect(provider).to receive(:shell_out!).with("rpm  -i #{package_source}", timeout: 900)
+
+            provider.action_upgrade
+          end
+        end
+
+        describe "when removing the package" do
+
+          let(:action) { :remove }
+
+          it "should do nothing" do
+            expect(provider).to_not receive(:shell_out!).with("rpm  -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900)
+            provider.action_remove
+          end
         end
-      end
 
-      it "installs with custom options specified in the resource" do
-        provider.candidate_version = '11'
-        new_resource.options("--dbpath /var/lib/rpm")
-        expect(provider).to receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
-        provider.install_package(new_resource.name, provider.candidate_version)
       end
+
+
     end
+  end
 
-    describe "when removing the package" do
-      it "should run rpm -e to remove the package" do
-        expect(provider).to receive(:shell_out!).with("rpm  -e ImageMagick-c++-6.5.4.7-7.el6_5")
-        provider.remove_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
-      end
+  context "when the resource name is the path to the package" do
+
+    let(:new_resource) do
+      # When we pass a source in as the name, then #initialize in the
+      # provider will call File.exists?. Because of the ordering in our
+      # let() bindings and such, we have to set the stub here and not in a
+      # before block.
+      allow(::File).to receive(:exists?).with(package_source).and_return(true)
+      Chef::Resource::Package.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+    end
+
+    let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
+
+    it "should install from a path when the package is a path and the source is nil" do
+      expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+      provider.current_resource = current_resource
+      expect(provider).to receive(:shell_out!).with("rpm  -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+      provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
+    end
+
+    it "should uprgrade from a path when the package is a path and the source is nil" do
+      expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+      current_resource.version("21.4-19.el5")
+      provider.current_resource = current_resource
+      expect(provider).to receive(:shell_out!).with("rpm  -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+      provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
     end
   end
+
+
 end
diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb
index 3805724..f790bdb 100644
--- a/spec/unit/provider/package/rubygems_spec.rb
+++ b/spec/unit/provider/package/rubygems_spec.rb
@@ -107,38 +107,6 @@ describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
     expect(@gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>= 0'))).to eq(Gem::Version.new('1.3.0'))
   end
 
-  context "when rubygems was upgraded from 1.8->2.0" do
-    # https://github.com/rubygems/rubygems/issues/404
-    # tl;dr rubygems 1.8 and 2.0 can both be in the load path, which means that
-    # require "rubygems/format" will load even though rubygems 2.0 doesn't have
-    # that file.
-
-    before do
-      if defined?(Gem::Format)
-        # tests are running under rubygems 1.8, or 2.0 upgraded from 1.8
-        @remove_gem_format = false
-      else
-        Gem.const_set(:Format, Object.new)
-        @remove_gem_format = true
-      end
-      allow(Gem::Package).to receive(:respond_to?).and_call_original
-      allow(Gem::Package).to receive(:respond_to?).with(:open).and_return(false)
-    end
-
-    after do
-      if @remove_gem_format
-        Gem.send(:remove_const, :Format)
-      end
-    end
-
-    it "finds a matching gem candidate version on rubygems 2.0+ with some rubygems 1.8 code loaded" do
-      package = double("Gem::Package", :spec => "a gemspec from package")
-      expect(Gem::Package).to receive(:new).with("/path/to/package.gem").and_return(package)
-      expect(@gem_env.spec_from_file("/path/to/package.gem")).to eq("a gemspec from package")
-    end
-
-  end
-
   it "gives the candidate version as nil if none is found" do
     dep = Gem::Dependency.new('rspec', '>= 0')
     latest = []
@@ -222,8 +190,6 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
   end
 
   it "uses the cached result for gem paths when available" do
-    gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR)
-    shell_out_result = OpenStruct.new(:stdout => gem_env_output)
     expect(@gem_env).not_to receive(:shell_out!)
     expected = ['/path/to/gems', '/another/path/to/gems']
     Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache['/usr/weird/bin/gem']= expected
@@ -261,7 +227,7 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
     else
       `which gem`.strip
     end
-    pending("cant find your gem executable") if path_to_gem.empty?
+    skip("cant find your gem executable") if path_to_gem.empty?
     gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new(path_to_gem)
     expected = ['rspec-core', Gem::Version.new(RSpec::Core::Version::STRING)]
     actual = gem_env.installed_versions(Gem::Dependency.new('rspec-core', nil)).map { |s| [s.name, s.version] }
@@ -403,6 +369,24 @@ describe Chef::Provider::Package::Rubygems do
       expect(provider.gem_env.gem_binary_location).to eq('/usr/weird/bin/gem')
     end
 
+    it "recognizes chef as omnibus" do
+      allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/chef/embedded/bin")
+      provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+      expect(provider.is_omnibus?).to be true
+    end
+
+    it "recognizes opscode as omnibus" do
+      allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/opscode/embedded/bin")
+      provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+      expect(provider.is_omnibus?).to be true
+    end
+
+    it "recognizes chefdk as omnibus" do
+      allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/chefdk/embedded/bin")
+      provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+      expect(provider.is_omnibus?).to be true
+    end
+
     it "searches for a gem binary when running on Omnibus on Unix" do
       platform_mock :unix do
         allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/chef/embedded/bin")
@@ -542,7 +526,7 @@ describe Chef::Provider::Package::Rubygems do
         it "installs the gem by shelling out when options are provided as a String" do
           @new_resource.options('-i /alt/install/location')
           expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" -i /alt/install/location"
-          expect(@provider).to receive(:shell_out!).with(expected, :env => nil)
+          expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900)
           @provider.run_action(:install)
           expect(@new_resource).to be_updated_by_last_action
         end
@@ -551,7 +535,7 @@ describe Chef::Provider::Package::Rubygems do
           @new_resource.gem_binary('/foo/bar')
           @new_resource.source('http://mirror.ops.rhcloud.com/mirror/ruby')
           expected ="/foo/bar install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" --source=#{@new_resource.source} --source=https://rubygems.org"
-          expect(@provider).to receive(:shell_out!).with(expected, :env => nil)
+          expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900)
           @provider.run_action(:install)
           expect(@new_resource).to be_updated_by_last_action
         end
@@ -561,7 +545,7 @@ describe Chef::Provider::Package::Rubygems do
           @new_resource.source('http://mirror.ops.rhcloud.com/mirror/ruby')
           @new_resource.clear_sources(true)
           expected ="/foo/bar install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" --clear-sources --source=#{@new_resource.source}"
-          expect(@provider).to receive(:shell_out!).with(expected, :env => nil)
+          expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900)
           @provider.run_action(:install)
           expect(@new_resource).to be_updated_by_last_action
         end
@@ -572,7 +556,7 @@ describe Chef::Provider::Package::Rubygems do
           it "installs the gem by shelling out when options are provided but no version is given" do
             @new_resource.options('-i /alt/install/location')
             expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@provider.candidate_version}\" -i /alt/install/location"
-            expect(@provider).to receive(:shell_out!).with(expected, :env => nil)
+            expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900)
             @provider.run_action(:install)
             expect(@new_resource).to be_updated_by_last_action
           end
@@ -618,7 +602,7 @@ describe Chef::Provider::Package::Rubygems do
       describe "in an alternate gem environment" do
         it "installs the gem by shelling out to gem install" do
           @new_resource.gem_binary('/usr/weird/bin/gem')
-          expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil)
+          expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", env: nil, timeout: 900)
           @provider.run_action(:install)
           expect(@new_resource).to be_updated_by_last_action
         end
@@ -627,7 +611,7 @@ describe Chef::Provider::Package::Rubygems do
           @new_resource.gem_binary('/usr/weird/bin/gem')
           @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
           @new_resource.version('>= 0')
-          expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil)
+          expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", env: nil, timeout: 900)
           @provider.run_action(:install)
           expect(@new_resource).to be_updated_by_last_action
         end
@@ -639,7 +623,7 @@ describe Chef::Provider::Package::Rubygems do
           @new_resource.gem_binary('/usr/weird/bin/gem')
           @new_resource.version('>= 0')
           expect(@new_resource.source).to eq(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
-          expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil)
+          expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", env: nil, timeout: 900)
           @provider.run_action(:install)
           expect(@new_resource).to be_updated_by_last_action
         end
@@ -678,7 +662,7 @@ describe Chef::Provider::Package::Rubygems do
 
         it "uninstalls via the gem command when options are given as a String" do
           @new_resource.options('-i /alt/install/location')
-          expect(@provider).to receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", :env=>nil)
+          expect(@provider).to receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", env: nil, timeout: 900)
           @provider.action_remove
         end
 
@@ -692,7 +676,7 @@ describe Chef::Provider::Package::Rubygems do
       describe "in an alternate gem environment" do
         it "uninstalls via the gem command" do
           @new_resource.gem_binary('/usr/weird/bin/gem')
-          expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", :env=>nil)
+          expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", env: nil, timeout: 900)
           @provider.action_remove
         end
       end
diff --git a/spec/unit/provider/package/smartos_spec.rb b/spec/unit/provider/package/smartos_spec.rb
index db39589..8f2d2bb 100644
--- a/spec/unit/provider/package/smartos_spec.rb
+++ b/spec/unit/provider/package/smartos_spec.rb
@@ -29,45 +29,45 @@ describe Chef::Provider::Package::SmartOS, "load_current_resource" do
     @current_resource = Chef::Resource::Package.new("varnish")
 
 
-	  @status = double("Status", :exitstatus => 0)
-		@provider = Chef::Provider::Package::SmartOS.new(@new_resource, @run_context)
-		allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
-		@stdin = StringIO.new
-		@stdout = "varnish-2.1.5nb2\n"
-		@stderr = StringIO.new
-		@pid = 10
-		@shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0)
+    @status = double("Status", :exitstatus => 0)
+    @provider = Chef::Provider::Package::SmartOS.new(@new_resource, @run_context)
+    allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
+    @stdin = StringIO.new
+    @stdout = "varnish-2.1.5nb2\n"
+    @stderr = StringIO.new
+    @pid = 10
+    @shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0)
   end
 
-	describe "when loading current resource" do
+  describe "when loading current resource" do
 
     it "should create a current resource with the name of the new_resource" do
-			expect(@provider).to receive(:shell_out!).and_return(@shell_out)
-			expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
-			@provider.load_current_resource
+      expect(@provider).to receive(:shell_out!).and_return(@shell_out)
+      expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
+      @provider.load_current_resource
     end
 
-		it "should set the current resource package name" do
-			expect(@provider).to receive(:shell_out!).and_return(@shell_out)
-			expect(@current_resource).to receive(:package_name).with(@new_resource.package_name)
-			@provider.load_current_resource
-		end
+    it "should set the current resource package name" do
+      expect(@provider).to receive(:shell_out!).and_return(@shell_out)
+      expect(@current_resource).to receive(:package_name).with(@new_resource.package_name)
+      @provider.load_current_resource
+    end
 
-		it "should set the installed version if it is installed" do
-		  expect(@provider).to receive(:shell_out!).and_return(@shell_out)
-			@provider.load_current_resource
-			expect(@current_resource.version).to eq("2.1.5nb2")
-	  end
+    it "should set the installed version if it is installed" do
+      expect(@provider).to receive(:shell_out!).and_return(@shell_out)
+      @provider.load_current_resource
+      expect(@current_resource.version).to eq("2.1.5nb2")
+    end
 
-		it "should set the installed version to nil if it's not installed" do
-			out = OpenStruct.new(:stdout => nil)
-			expect(@provider).to receive(:shell_out!).and_return(out)
-			@provider.load_current_resource
-			expect(@current_resource.version).to eq(nil)
-		end
+    it "should set the installed version to nil if it's not installed" do
+      out = OpenStruct.new(:stdout => nil)
+      expect(@provider).to receive(:shell_out!).and_return(out)
+      @provider.load_current_resource
+      expect(@current_resource.version).to eq(nil)
+    end
 
 
-	end
+  end
 
   describe "candidate_version" do
     it "should return the candidate_version variable if already setup" do
@@ -76,27 +76,37 @@ describe Chef::Provider::Package::SmartOS, "load_current_resource" do
       @provider.candidate_version
     end
 
-    it "should lookup the candidate_version if the variable is not already set" do
+    it "should lookup the candidate_version if the variable is not already set (pkgin separated by spaces)" do
       search = double()
       expect(search).to receive(:each_line).
-        and_yield("something-varnish-1.1.1  something varnish like\n").
-        and_yield("varnish-2.3.4 actual varnish\n")
+        and_yield("something-varnish-1.1.1   something varnish like\n").
+        and_yield("varnish-2.3.4             actual varnish\n")
       @shell_out = double('shell_out!', :stdout => search)
-      expect(@provider).to receive(:shell_out!).with('/opt/local/bin/pkgin se varnish', :env => nil, :returns => [0,1]).and_return(@shell_out)
+      expect(@provider).to receive(:shell_out!).with('/opt/local/bin/pkgin', 'se', 'varnish', :env => nil, :returns => [0,1], :timeout=>900).and_return(@shell_out)
+      expect(@provider.candidate_version).to eq("2.3.4")
+    end
+
+    it "should lookup the candidate_version if the variable is not already set (pkgin separated by semicolons)" do
+      search = double()
+      expect(search).to receive(:each_line).
+        and_yield("something-varnish-1.1.1;;something varnish like\n").
+        and_yield("varnish-2.3.4;;actual varnish\n")
+      @shell_out = double('shell_out!', :stdout => search)
+      expect(@provider).to receive(:shell_out!).with('/opt/local/bin/pkgin', 'se', 'varnish', :env => nil, :returns => [0,1], :timeout=>900).and_return(@shell_out)
       expect(@provider.candidate_version).to eq("2.3.4")
     end
   end
 
-	describe "when manipulating a resource" do
+  describe "when manipulating a resource" do
 
-		it "run pkgin and install the package" do
-			out = OpenStruct.new(:stdout => nil)
-      expect(@provider).to receive(:shell_out!).with("/opt/local/sbin/pkg_info -E \"varnish*\"", {:env => nil, :returns=>[0,1]}).and_return(@shell_out)
-      expect(@provider).to receive(:shell_out!).with("/opt/local/bin/pkgin -y install varnish-2.1.5nb2", {:env=>nil}).and_return(out)
+    it "run pkgin and install the package" do
+      out = OpenStruct.new(:stdout => nil)
+      expect(@provider).to receive(:shell_out!).with("/opt/local/sbin/pkg_info", "-E", "varnish*", {:env => nil, :returns=>[0,1], :timeout=>900}).and_return(@shell_out)
+      expect(@provider).to receive(:shell_out!).with("/opt/local/bin/pkgin", "-y", "install", "varnish-2.1.5nb2", {:env=>nil, :timeout=>900}).and_return(out)
       @provider.load_current_resource
       @provider.install_package("varnish", "2.1.5nb2")
-		end
+    end
 
-	end
+  end
 
 end
diff --git a/spec/unit/provider/package/solaris_spec.rb b/spec/unit/provider/package/solaris_spec.rb
index c348d66..ae6c96d 100644
--- a/spec/unit/provider/package/solaris_spec.rb
+++ b/spec/unit/provider/package/solaris_spec.rb
@@ -71,8 +71,8 @@ PKGINFO
 
     it "should get the source package version from pkginfo if provided" do
       status = double(:stdout => @pkginfo, :exitstatus => 0)
-      expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(status)
-      expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash").and_return(@status)
+      expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(status)
+      expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(@status)
       @provider.load_current_resource
 
       expect(@provider.current_resource.package_name).to eq("SUNWbash")
@@ -81,8 +81,8 @@ PKGINFO
 
     it "should return the current version installed if found by pkginfo" do
       status = double(:stdout => @pkginfo, :exitstatus => 0)
-      expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status)
-      expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash").and_return(status)
+      expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(@status)
+      expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(status)
       @provider.load_current_resource
       expect(@provider.current_resource.version).to eq("11.10.0,REV=2005.01.08.05.16")
     end
@@ -101,8 +101,8 @@ PKGINFO
     end
 
     it "should return a current resource with a nil version if the package is not found" do
-      expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status)
-      expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash").and_return(@status)
+      expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(@status)
+      expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(@status)
       @provider.load_current_resource
       expect(@provider.current_resource.version).to be_nil
     end
@@ -132,7 +132,7 @@ PKGINFO
 
   describe "install and upgrade" do
     it "should run pkgadd -n -d with the package source to install" do
-      expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all")
+      expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all", { timeout: 900 })
       @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
     end
 
@@ -140,26 +140,26 @@ PKGINFO
       @new_resource = Chef::Resource::Package.new("/tmp/bash.pkg")
       @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context)
       expect(@new_resource.source).to eq("/tmp/bash.pkg")
-      expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all")
+      expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all", { timeout: 900 })
       @provider.install_package("/tmp/bash.pkg", "11.10.0,REV=2005.01.08.05.16")
     end
 
     it "should run pkgadd -n -a /tmp/myadmin -d with the package options -a /tmp/myadmin" do
       allow(@new_resource).to receive(:options).and_return("-a /tmp/myadmin")
-      expect(@provider).to receive(:shell_out!).with("pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all")
+      expect(@provider).to receive(:shell_out!).with("pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all", { timeout: 900 })
       @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
     end
   end
 
   describe "remove" do
     it "should run pkgrm -n to remove the package" do
-      expect(@provider).to receive(:shell_out!).with("pkgrm -n SUNWbash")
+      expect(@provider).to receive(:shell_out!).with("pkgrm -n SUNWbash", { timeout: 900 })
       @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
     end
 
     it "should run pkgrm -n -a /tmp/myadmin with options -a /tmp/myadmin" do
       allow(@new_resource).to receive(:options).and_return("-a /tmp/myadmin")
-      expect(@provider).to receive(:shell_out!).with("pkgrm -n -a /tmp/myadmin SUNWbash")
+      expect(@provider).to receive(:shell_out!).with("pkgrm -n -a /tmp/myadmin SUNWbash", { timeout: 900 })
       @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
     end
 
diff --git a/spec/unit/provider/package/windows/exe_spec.rb b/spec/unit/provider/package/windows/exe_spec.rb
new file mode 100644
index 0000000..730df5e
--- /dev/null
+++ b/spec/unit/provider/package/windows/exe_spec.rb
@@ -0,0 +1,251 @@
+#
+# Author:: Matt Wrock <matt at mattwrock.com>
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+require 'chef/provider/package/windows/exe'
+
+unless Chef::Platform.windows?
+  class Chef
+    module ReservedNames::Win32
+      class File
+        def version_info
+          nil
+        end
+      end
+    end
+  end
+end
+
+describe Chef::Provider::Package::Windows::Exe do
+  let(:package_name) { "calculator" }
+  let(:resource_source) { "calculator.exe" }
+  let(:new_resource) do
+    new_resource = Chef::Resource::WindowsPackage.new(package_name)
+    new_resource.source(resource_source)
+    new_resource
+  end
+  let(:uninstall_hash) do
+    [{
+      'DisplayVersion' => 'outdated',
+      'UninstallString' => File.join("uninst_dir", "uninst_file")
+    }]
+  end
+  let(:uninstall_entry) do
+    entries = []
+    uninstall_hash.each do |entry|
+      entries.push(Chef::Provider::Package::Windows::RegistryUninstallEntry.new('hive', 'key', entry))
+    end
+    entries
+  end
+  let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :nsis, uninstall_entry) }
+  let(:file_version) { nil }
+  let(:product_version) { nil }
+  let(:version_info) { instance_double("Chef::ReservedNames::Win32::File::Version_info", FileVersion: file_version, ProductVersion: product_version) }
+
+  before(:each) do
+    allow(Chef::ReservedNames::Win32::File).to receive(:version_info).and_return(version_info)
+    allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(true)
+  end
+
+  it "responds to shell_out!" do
+    expect(provider).to respond_to(:shell_out!)
+  end
+
+  describe "expand_options" do
+    it "returns an empty string if passed no options" do
+      expect(provider.expand_options(nil)).to eql ""
+    end
+
+    it "returns a string with a leading space if passed options" do
+      expect(provider.expand_options("--train nope --town no_way")).to eql(" --train nope --town no_way")
+    end
+  end
+
+  describe "installed_version" do
+    it "returns the installed version" do
+      expect(provider.installed_version).to eql(["outdated"])
+    end
+
+    context "no versions installed" do
+      let(:uninstall_hash) { [] }
+
+      it "returns the installed version" do
+        expect(provider.installed_version).to eql(nil)
+      end
+    end
+  end
+
+  describe "package_version" do
+    before { new_resource.version(nil) }
+
+    context "source file does not exist" do
+      before do
+        allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false)
+      end
+
+      it "returns nil" do
+        expect(provider.package_version).to eql(nil)
+      end
+    end
+
+    context "file version is empty" do
+      let(:file_version) { '' }
+
+      it "returns nil" do
+        expect(provider.package_version).to eql(nil)
+      end
+
+      it "returns the version of a package if given" do
+        new_resource.version('v55555')
+        expect(provider.package_version).to eql('v55555')
+      end
+    end
+
+    context "both file and product version are in installer" do
+      let(:file_version) { '1.1.1' }
+      let(:product_version) { '1.1' }
+
+      it "returns the file version" do
+        expect(provider.package_version).to eql('1.1.1')
+      end
+
+      it "returns the version of a package if given" do
+        new_resource.version('v55555')
+        expect(provider.package_version).to eql('v55555')
+      end
+    end
+
+    context "only file version is in installer" do
+      let(:file_version) { '1.1.1' }
+
+      it "returns the file version" do
+        expect(provider.package_version).to eql('1.1.1')
+      end
+
+      it "returns the version of a package if given" do
+        new_resource.version('v55555')
+        expect(provider.package_version).to eql('v55555')
+      end
+    end
+
+    context "only product version is in installer" do
+      let(:product_version) { '1.1' }
+
+      it "returns the product version" do
+        expect(provider.package_version).to eql('1.1')
+      end
+
+      it "returns the version of a package if given" do
+        new_resource.version('v55555')
+        expect(provider.package_version).to eql('v55555')
+      end
+    end
+
+    context "no version info is in installer" do
+      let(:file_version) { nil }
+      let(:product_version) { nil }
+
+      it "returns the version of a package" do
+        new_resource.version('v55555')
+        expect(provider.package_version).to eql('v55555')
+      end
+    end
+
+    context "no version info is in installer and none in attribute" do
+      it "returns the version of a package" do
+        expect(provider.package_version).to eql(nil)
+      end
+    end
+  end
+
+  describe "remove_package" do
+    context "no version given and one package installed" do
+      it "removes installed package" do
+        expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \/d\"uninst_dir\" uninst_file \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash))
+        provider.remove_package
+      end
+    end
+
+    context "several packages installed" do
+      let(:uninstall_hash) do
+        [
+          {
+          'DisplayVersion' => 'v1',
+          'UninstallString' => File.join("uninst_dir1", "uninst_file1")
+          },
+          {
+          'DisplayVersion' => 'v2',
+          'UninstallString' => File.join("uninst_dir2", "uninst_file2")
+          }
+        ]
+      end
+
+      context "version given and installed" do
+        it "removes given version" do
+          new_resource.version('v2')
+          expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \/d\"uninst_dir2\" uninst_file2 \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash))
+          provider.remove_package
+        end
+      end
+
+      context "no version given" do
+        it "removes both versions" do
+          expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \/d\"uninst_dir1\" uninst_file1 \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash))
+          expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \/d\"uninst_dir2\" uninst_file2 \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash))
+          provider.remove_package
+        end
+      end
+    end
+  end
+
+  context "installs nsis installer" do
+    let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :nsis, uninstall_entry) }
+
+    it "calls installer with the correct flags" do
+      expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"#{Regexp.quote(new_resource.source)}\" \/S \/NCRC  & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash))
+      provider.install_package
+    end
+  end
+
+  context "installs installshield installer" do
+    let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :installshield, uninstall_entry) }
+
+    it "calls installer with the correct flags" do
+      expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"#{Regexp.quote(new_resource.source)}\" \/s \/sms  & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash))
+      provider.install_package
+    end
+  end
+
+  context "installs inno installer" do
+    let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :inno, uninstall_entry) }
+
+    it "calls installer with the correct flags" do
+      expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"#{Regexp.quote(new_resource.source)}\" \/VERYSILENT \/SUPPRESSMSGBOXES \/NORESTART  & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash))
+      provider.install_package
+    end
+  end
+
+  context "installs wise installer" do
+    let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :wise, uninstall_entry) }
+
+    it "calls installer with the correct flags" do
+      expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"#{Regexp.quote(new_resource.source)}\" \/s  & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash))
+      provider.install_package
+    end
+  end
+end
diff --git a/spec/unit/provider/package/windows/msi_spec.rb b/spec/unit/provider/package/windows/msi_spec.rb
index bef2028..9377dca 100644
--- a/spec/unit/provider/package/windows/msi_spec.rb
+++ b/spec/unit/provider/package/windows/msi_spec.rb
@@ -17,17 +17,37 @@
 #
 
 require 'spec_helper'
+require 'chef/provider/package/windows/msi'
 
 describe Chef::Provider::Package::Windows::MSI do
   let(:node) { double('Chef::Node') }
   let(:events) { double('Chef::Events').as_null_object }  # mock all the methods
   let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
-  let(:new_resource) { Chef::Resource::WindowsPackage.new("calculator.msi") }
-  let(:provider) { Chef::Provider::Package::Windows::MSI.new(new_resource) }
-
-  before(:each) do
-    stub_const("File::ALT_SEPARATOR", "\\")
-    allow(::File).to receive(:absolute_path).with("calculator.msi").and_return("calculator.msi")
+  let(:package_name) { "calculator" }
+  let(:resource_source) { "calculator.msi" }
+  let(:resource_version) { nil }
+  let(:new_resource) do
+    new_resource = Chef::Resource::WindowsPackage.new(package_name)
+    new_resource.source(resource_source)
+    new_resource.version(resource_version)
+    new_resource
+  end
+  let(:uninstall_hash) do
+    [{
+      'DisplayVersion' => 'outdated',
+      'UninstallString' => "MsiExec.exe /X{guid}"
+    }]
+  end
+  let(:uninstall_entry) do
+    entries = []
+    uninstall_hash.each do |entry|
+      entries.push(Chef::Provider::Package::Windows::RegistryUninstallEntry.new('hive', 'key', entry))
+    end
+    entries
+  end
+  let(:provider) { Chef::Provider::Package::Windows::MSI.new(new_resource, uninstall_entry) }
+  before do
+    allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(true)
   end
 
   it "responds to shell_out!" do
@@ -50,6 +70,11 @@ describe Chef::Provider::Package::Windows::MSI do
       allow(provider).to receive(:get_installed_version).with("{23170F69-40C1-2702-0920-000001000000}").and_return("3.14159.1337.42")
       expect(provider.installed_version).to eql("3.14159.1337.42")
     end
+
+    it "returns the installed version in the registry when install file not present" do
+      allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false)
+      expect(provider.installed_version).to eql(["outdated"])
+    end
   end
 
   describe "package_version" do
@@ -57,19 +82,78 @@ describe Chef::Provider::Package::Windows::MSI do
       allow(provider).to receive(:get_product_property).with(/calculator.msi$/, "ProductVersion").and_return(42)
       expect(provider.package_version).to eql(42)
     end
+
+    context "version is explicitly provided" do
+      let(:resource_version) { "given_version" }
+      
+      it "returns the given version" do
+        expect(provider.package_version).to eql("given_version")
+      end
+    end
+
+    context "no source or version is given" do
+      before do
+        allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false)
+      end
+      
+      it "returns nil" do
+        expect(provider.package_version).to eql(nil)
+      end
+    end
   end
 
   describe "install_package" do
     it "calls msiexec /qn /i" do
-      expect(provider).to receive(:shell_out!).with(/msiexec \/qn \/i \"calculator.msi\"/, kind_of(Hash))
-      provider.install_package("unused", "unused")
+      expect(provider).to receive(:shell_out!).with(/msiexec \/qn \/i \"#{Regexp.quote(new_resource.source)}\"/, kind_of(Hash))
+      provider.install_package
     end
   end
 
   describe "remove_package" do
     it "calls msiexec /qn /x" do
-      expect(provider).to receive(:shell_out!).with(/msiexec \/qn \/x \"calculator.msi\"/, kind_of(Hash))
-      provider.remove_package("unused", "unused")
+      expect(provider).to receive(:shell_out!).with(/msiexec \/qn \/x \"#{Regexp.quote(new_resource.source)}\"/, kind_of(Hash))
+      provider.remove_package
+    end
+
+    context "no source is provided" do
+      before do
+        allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false)
+      end
+
+      it "removes installed package" do
+        expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid} \/Q/, kind_of(Hash))
+        provider.remove_package
+      end
+
+      context "there are multiple installs" do
+        let(:uninstall_hash) do
+          [
+            {
+              'DisplayVersion' => 'outdated',
+              'UninstallString' => "MsiExec.exe /X{guid}"
+            },
+            {
+              'DisplayVersion' => 'really_outdated',
+              'UninstallString' => "MsiExec.exe /X{guid2}"
+            }
+          ]
+        end
+
+        it "removes both installed package" do
+          expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid} \/Q/, kind_of(Hash))
+          expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid2} \/Q/, kind_of(Hash))
+          provider.remove_package
+        end
+      end
+
+      context "custom options includes /Q" do
+        before { new_resource.options("/Q") }
+
+        it "does not duplicate quiet switch" do
+          expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid} \/Q/, kind_of(Hash))
+          provider.remove_package
+        end
+      end
     end
   end
 end
diff --git a/spec/unit/provider/package/windows_spec.rb b/spec/unit/provider/package/windows_spec.rb
index d402113..c26c446 100644
--- a/spec/unit/provider/package/windows_spec.rb
+++ b/spec/unit/provider/package/windows_spec.rb
@@ -17,70 +17,350 @@
 #
 
 require 'spec_helper'
+require 'chef/provider/package/windows/exe'
+require 'chef/provider/package/windows/msi'
 
 describe Chef::Provider::Package::Windows, :windows_only do
+  before(:each) do
+    allow(Chef::Util::PathHelper).to receive(:windows?).and_return(true)
+    allow(Chef::FileCache).to receive(:create_cache_path).with("package/").and_return(cache_path)
+  end
+
   let(:node) { double('Chef::Node') }
   let(:events) { double('Chef::Events').as_null_object }  # mock all the methods
   let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
-  let(:new_resource) { Chef::Resource::WindowsPackage.new("calculator.msi") }
+  let(:resource_source) { 'calculator.msi' }
+  let(:resource_name) { 'calculator' }
+  let(:new_resource) do
+    new_resource = Chef::Resource::WindowsPackage.new(resource_name)
+    new_resource.source(resource_source)
+    new_resource
+  end
   let(:provider) { Chef::Provider::Package::Windows.new(new_resource, run_context) }
+  let(:cache_path) { 'c:\\cache\\' }
+
+  before(:each) do
+    allow(::File).to receive(:exist?).with(provider.new_resource.source).and_return(true)
+  end
 
   describe "load_current_resource" do
-    before(:each) do
-      allow(Chef::Util::PathHelper).to receive(:validate_path)
-      allow(provider).to receive(:package_provider).and_return(double('package_provider',
+    shared_examples "a local file" do
+      before(:each) do
+        allow(Chef::Util::PathHelper).to receive(:validate_path)
+        allow(provider).to receive(:package_provider).and_return(double('package_provider',
           :installed_version => "1.0", :package_version => "2.0"))
-    end
+      end
 
-    it "creates a current resource with the name of the new resource" do
-      provider.load_current_resource
-      expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage)
-      expect(provider.current_resource.name).to eql("calculator.msi")
-    end
+      it "creates a current resource with the name of the new resource" do
+        provider.load_current_resource
+        expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage)
+        expect(provider.current_resource.name).to eql(resource_name)
+      end
+
+      it "sets the current version if the package is installed" do
+        provider.load_current_resource
+        expect(provider.current_resource.version).to eql("1.0")
+      end
 
-    it "sets the current version if the package is installed" do
-      provider.load_current_resource
-      expect(provider.current_resource.version).to eql("1.0")
+      it "sets the version to be installed" do
+        provider.load_current_resource
+        expect(provider.new_resource.version).to eql("2.0")
+      end
     end
 
-    it "sets the version to be installed" do
-      provider.load_current_resource
-      expect(provider.new_resource.version).to eql("2.0")
+    context "when the source is a uri" do
+      let(:resource_source) { 'https://foo.bar/calculator.msi' }
+
+      context "when the source has not been downloaded" do
+        before(:each) do
+          allow(provider).to receive(:downloadable_file_missing?).and_return(true)
+        end
+        it "sets the current version to unknown" do
+          provider.load_current_resource
+          expect(provider.current_resource.version).to eql("unknown")
+        end
+      end
+
+      context "when the source has been downloaded" do
+        before(:each) do
+          allow(provider).to receive(:downloadable_file_missing?).and_return(false)
+        end
+        it_behaves_like "a local file"
+      end
     end
 
-    it "checks that the source path is valid" do
-      expect(Chef::Util::PathHelper).to receive(:validate_path)
-      provider.load_current_resource
+    context "when source is a local file" do
+      it_behaves_like "a local file"
     end
   end
 
   describe "package_provider" do
-    it "sets the package provider to MSI if the the installer type is :msi" do
-      allow(provider).to receive(:installer_type).and_return(:msi)
-      expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI)
+    shared_examples "a local file" do
+
+      it "checks that the source path is valid" do
+        expect(Chef::Util::PathHelper).to receive(:validate_path)
+        provider.package_provider
+      end
+
+      it "sets the package provider to MSI if the the installer type is :msi" do
+        allow(provider).to receive(:installer_type).and_return(:msi)
+        expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI)
+      end
+
+      it "sets the package provider to Exe if the the installer type is :inno" do
+        allow(provider).to receive(:installer_type).and_return(:inno)
+        expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe)
+      end
+
+      it "sets the package provider to Exe if the the installer type is :nsis" do
+        allow(provider).to receive(:installer_type).and_return(:nsis)
+        expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe)
+      end
+
+      it "sets the package provider to Exe if the the installer type is :wise" do
+        allow(provider).to receive(:installer_type).and_return(:wise)
+        expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe)
+      end
+
+      it "sets the package provider to Exe if the the installer type is :installshield" do
+        allow(provider).to receive(:installer_type).and_return(:installshield)
+        expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe)
+      end
+
+      it "defaults to exe if the installer_type is unknown" do
+        allow(provider).to receive(:installer_type).and_return(nil)
+        expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe)
+      end
+    end
+
+    context "when the source is a uri" do
+      let(:resource_source) { 'https://foo.bar/calculator.msi' }
+
+      context "when the source has not been downloaded" do
+        before(:each) do
+          allow(provider).to receive(:should_download?).and_return(true)
+        end
+
+        it "should create a package provider with source pointing at the local file" do
+          expect(Chef::Provider::Package::Windows::MSI).to receive(:new) do |r|
+            expect(r.source).to eq("#{cache_path}#{::File.basename(resource_source)}")
+          end
+          provider.package_provider
+        end
+
+        it_behaves_like "a local file"
+      end
+
+      context "when the source has been downloaded" do
+        before(:each) do
+          allow(provider).to receive(:should_download?).and_return(false)
+        end
+        it_behaves_like "a local file"
+      end
     end
 
-    it "raises an error if the installer_type is unknown" do
-      allow(provider).to receive(:installer_type).and_return(:apt_for_windows)
-      expect { provider.package_provider }.to raise_error
+    context "when source is a local file" do
+      it_behaves_like "a local file"
     end
   end
 
   describe "installer_type" do
-    it "it returns @installer_type if it is set" do
+    let(:resource_source) { "microsoft_installer.exe" }
+
+    context "there is no source" do
+      let(:uninstall_hash) do
+        [{
+          'DisplayVersion' => 'outdated',
+          'UninstallString' => "blah blah"
+        }]
+      end
+      let(:uninstall_key) { "blah" }
+      let(:uninstall_entry) do
+        entries = []
+        uninstall_hash.each do |entry|
+          entries.push(Chef::Provider::Package::Windows::RegistryUninstallEntry.new('hive', uninstall_key, entry))
+        end
+        entries
+      end
+
+      before do
+        allow(Chef::Provider::Package::Windows::RegistryUninstallEntry).to receive(:find_entries).and_return(uninstall_entry)
+        allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false)
+      end
+
+      context "uninstall string contains MsiExec.exe" do
+        let(:uninstall_hash) do
+          [{
+            'DisplayVersion' => 'outdated',
+            'UninstallString' => "MsiExec.exe /X{guid}"
+          }]
+        end
+
+        it "sets installer_type to MSI" do
+          expect(provider.installer_type).to eql(:msi)
+        end
+      end
+
+      context "uninstall string ends with uninst.exe" do
+        let(:uninstall_hash) do
+          [{
+            'DisplayVersion' => 'outdated',
+            'UninstallString' => %q{"c:/hfhfheru/uninst.exe"}
+          }]
+        end
+
+        it "sets installer_type to NSIS" do
+          expect(provider.installer_type).to eql(:nsis)
+        end
+      end
+
+      context "uninstall key ends in _is1" do
+        let(:uninstall_key) { "blah_is1" }
+
+        it "sets installer_type to inno" do
+          expect(provider.installer_type).to eql(:inno)
+        end
+      end
+
+      context "eninstall entries is empty" do
+        before { allow(Chef::Provider::Package::Windows::RegistryUninstallEntry).to receive(:find_entries).and_return([]) }
+
+        it "returns nil" do
+          expect(provider.installer_type).to eql(nil)
+        end
+      end
+    end
+
+    it "returns @installer_type if it is set" do
       provider.new_resource.installer_type(:downeaster)
       expect(provider.installer_type).to eql(:downeaster)
     end
 
-    it "sets installer_type to msi if the source ends in .msi" do
-      provider.new_resource.source("microsoft_installer.msi")
-      expect(provider.installer_type).to eql(:msi)
+    it "sets installer_type to inno if the source contains inno" do
+      allow(::Kernel).to receive(:open).and_yield(StringIO.new('blah blah inno blah'))
+      expect(provider.installer_type).to eql(:inno)
+    end
+
+    it "sets installer_type to wise if the source contains wise" do
+      allow(::Kernel).to receive(:open).and_yield(StringIO.new('blah blah wise blah'))
+      expect(provider.installer_type).to eql(:wise)
+    end
+
+    it "sets installer_type to nsis if the source contains nsis" do
+      allow(::Kernel).to receive(:open).and_yield(StringIO.new('blah blah nullsoft blah'))
+      expect(provider.installer_type).to eql(:nsis)
+    end
+
+    context "source ends in .msi" do
+      let(:resource_source) { "microsoft_installer.msi" }
+
+      it "sets installer_type to msi" do
+        expect(provider.installer_type).to eql(:msi)
+      end
+    end
+
+    context "the source is setup.exe" do
+      let(:resource_source) { "setup.exe" }
+
+      it "sets installer_type to installshield" do
+        allow(::Kernel).to receive(:open).and_yield(StringIO.new(''))
+        expect(provider.installer_type).to eql(:installshield)
+      end
+    end
+
+    context "cannot determine the installer type" do
+      let(:resource_source) { "tomfoolery.now" }
+
+      it "raises an error" do
+        allow(::Kernel).to receive(:open).and_yield(StringIO.new(''))
+        provider.new_resource.installer_type(nil)
+        expect { provider.installer_type }.to raise_error(Chef::Exceptions::CannotDetermineWindowsInstallerType)
+      end
     end
+  end
+
+  describe "action_install" do
+    let(:new_resource) { Chef::Resource::WindowsPackage.new("blah.exe") }
+    before do
+      new_resource.installer_type(:inno)
+      allow_any_instance_of(Chef::Provider::Package::Windows::Exe).to receive(:package_version).and_return(new_resource.version)
+    end
+
+    context "no version given, discovered or installed" do
+      it "installs latest" do
+        expect(provider).to receive(:install_package).with("blah.exe", "latest")
+        provider.run_action(:install)
+      end
+    end
+
+    context "no version given or discovered but package is installed" do
+      before { allow(provider).to receive(:current_version_array).and_return(["5.5.5"]) }
+
+      it "does not install" do
+        expect(provider).not_to receive(:install_package)
+        provider.run_action(:install)
+      end
+    end
+
+    context "a version is given and none is installed" do
+      before { new_resource.version('5.5.5') }
+
+      it "installs given version" do
+        expect(provider).to receive(:install_package).with("blah.exe", "5.5.5")
+        provider.run_action(:install)
+      end
+    end
+
+    context "a version is given and several are installed" do
+      context "given version matches an installed version" do
+        before do
+          new_resource.version('5.5.5')
+          allow(provider).to receive(:current_version_array).and_return([ ["5.5.5", "4.3.0", "1.1.1"] ])
+        end
+        
+        it "does not install" do
+          expect(provider).not_to receive(:install_package)
+          provider.run_action(:install)
+        end
+      end
+
+      context "given version does not match an installed version" do
+        before do
+          new_resource.version('5.5.5')
+          allow(provider).to receive(:current_version_array).and_return([ ["5.5.0", "4.3.0", "1.1.1"] ])
+        end
+        
+        it "installs given version" do
+          expect(provider).to receive(:install_package).with("blah.exe", "5.5.5")
+          provider.run_action(:install)
+        end
+      end
+    end
+
+    context "a version is given and one is installed" do
+      context "given version matches installed version" do
+        before do
+          new_resource.version('5.5.5')
+          allow(provider).to receive(:current_version_array).and_return(["5.5.5"])
+        end
+        
+        it "does not install" do
+          expect(provider).not_to receive(:install_package)
+          provider.run_action(:install)
+        end
+      end
 
-    it "raises an error if it cannot determine the installer type" do
-      provider.new_resource.installer_type(nil)
-      provider.new_resource.source("tomfoolery.now")
-      expect { provider.installer_type }.to raise_error(ArgumentError)
+      context "given version does not match installed version" do
+        before do
+          new_resource.version('5.5.5')
+          allow(provider).to receive(:current_version_array).and_return(["5.5.0"])
+        end
+        
+        it "installs given version" do
+          expect(provider).to receive(:install_package).with("blah.exe", "5.5.5")
+          provider.run_action(:install)
+        end
+      end
     end
   end
 end
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
index 865dce2..b37a675 100644
--- a/spec/unit/provider/package/yum_spec.rb
+++ b/spec/unit/provider/package/yum_spec.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,13 +17,14 @@
 #
 
 require 'spec_helper'
+require 'securerandom'
 
 describe Chef::Provider::Package::Yum do
   before(:each) do
     @node = Chef::Node.new
     @events = Chef::EventDispatch::Dispatcher.new
     @run_context = Chef::RunContext.new(@node, {}, @events)
-    @new_resource = Chef::Resource::Package.new('cups')
+    @new_resource = Chef::Resource::YumPackage.new('cups')
     @status = double("Status", :exitstatus => 0)
     @yum_cache = double(
       'Chef::Provider::Yum::YumCache',
@@ -38,6 +39,7 @@ describe Chef::Provider::Package::Yum do
       :disable_extra_repo_control => true
     )
     allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+    allow(@yum_cache).to receive(:yum_binary=).with("yum")
     @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
     @pid = double("PID")
   end
@@ -87,6 +89,46 @@ describe Chef::Provider::Package::Yum do
       end
     end
 
+    describe "yum_binary accessor" do
+      it "when yum-deprecated exists" do
+        expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(true)
+        expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated")
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        expect(@provider.yum_binary).to eql("yum-deprecated")
+      end
+
+      it "when yum-deprecated does not exist" do
+        expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(false)
+        expect(@yum_cache).to receive(:yum_binary=).with("yum")
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        expect(@provider.yum_binary).to eql("yum")
+      end
+
+      it "when the yum_binary is set on the resource" do
+        @new_resource.yum_binary "/usr/bin/yum-something"
+        expect(File).not_to receive(:exist?)
+        expect(@yum_cache).to receive(:yum_binary=).with("/usr/bin/yum-something")
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        expect(@provider.yum_binary).to eql("/usr/bin/yum-something")
+      end
+
+      it "when the new_resource is a vanilla package class and yum-deprecated exists" do
+        @new_resource = Chef::Resource::Package.new('cups')
+        expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(true)
+        expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated")
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        expect(@provider.yum_binary).to eql("yum-deprecated")
+      end
+
+      it "when the new_resource is a vanilla package class and yum-deprecated does not exist" do
+        @new_resource = Chef::Resource::Package.new('cups')
+        expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(false)
+        expect(@yum_cache).to receive(:yum_binary=).with("yum")
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        expect(@provider.yum_binary).to eql("yum")
+      end
+    end
+
     describe "when arch in package_name" do
       it "should set the arch if no existing package_name is found and new_package_name+new_arch is available" do
         @new_resource = Chef::Resource::YumPackage.new('testing.noarch')
@@ -108,6 +150,7 @@ describe Chef::Provider::Package::Yum do
         allow(@yum_cache).to receive(:package_available?).and_return(true)
         allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true)
         allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+        allow(@yum_cache).to receive(:yum_binary=).with("yum")
         @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
         @provider.load_current_resource
         expect(@provider.new_resource.package_name).to eq("testing")
@@ -122,6 +165,26 @@ describe Chef::Provider::Package::Yum do
         expect(@provider.arch).to eq("noarch")
       end
 
+      describe "when version constraint in package_name" do
+        it "should set package_version if no existing package_name is found and new_package_name is available" do
+          @new_resource = Chef::Resource::Package.new('cups = 1.2.4-11.18.el5_2.3')
+          @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+          allow(@yum_cache).to receive(:package_available?) { |pkg| pkg == 'cups' ? true : false }
+          allow(@yum_cache).to receive(:packages_from_require) do |pkg|
+            [Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.3", "noarch", [], false, true, "base"),
+            Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.2", "noarch", [], false, true, "base"),]
+          end
+          expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{checking yum info})
+          expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{installed version})
+          expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{matched 2 packages,})
+          @provider.load_current_resource
+          expect(@provider.new_resource.package_name).to eq("cups")
+          expect(@provider.new_resource.version).to eq("1.2.4-11.18.el5_2.3")
+          expect(@provider.send(:new_version_array)).to eq(["1.2.4-11.18.el5_2.3"])
+          expect(@provider.send(:package_name_array)).to eq(["cups"])
+        end
+      end
+
       it "should not set the arch when an existing package_name is found" do
         @new_resource = Chef::Resource::YumPackage.new('testing.beta3')
         @yum_cache = double(
@@ -142,6 +205,7 @@ describe Chef::Provider::Package::Yum do
         allow(@yum_cache).to receive(:package_available?).and_return(true)
         allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true)
         allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+        allow(@yum_cache).to receive(:yum_binary=).with("yum")
         @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
         # annoying side effect of the fun stub'ing above
         @provider.load_current_resource
@@ -173,6 +237,7 @@ describe Chef::Provider::Package::Yum do
         allow(@yum_cache).to receive(:package_available?).and_return(true)
         allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true)
         allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+        allow(@yum_cache).to receive(:yum_binary=).with("yum")
         @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
         @provider.load_current_resource
         expect(@provider.new_resource.package_name).to eq("testing.beta3")
@@ -208,6 +273,7 @@ describe Chef::Provider::Package::Yum do
         allow(@yum_cache).to receive(:package_available?).and_return(true)
         allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true)
         allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+        allow(@yum_cache).to receive(:yum_binary=).with("yum")
         @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
         @provider.load_current_resource
         expect(@provider.new_resource.package_name).to eq("testing.i386")
@@ -260,6 +326,7 @@ describe Chef::Provider::Package::Yum do
 
       before do
         allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(yum_cache)
+        allow(yum_cache).to receive(:yum_binary=).with("yum")
         @pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "2.0.1.el5", "x86_64", [])
         expect(yum_cache).to receive(:packages_from_require).and_return([@pkg])
       end
@@ -331,6 +398,7 @@ describe Chef::Provider::Package::Yum do
           :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+        allow(@yum_cache).to receive(:yum_binary=).with("yum")
       pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "2.0.1.el5", "x86_64", [])
       expect(@yum_cache).to receive(:packages_from_require).and_return([pkg])
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@@ -352,6 +420,7 @@ describe Chef::Provider::Package::Yum do
         :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+        allow(@yum_cache).to receive(:yum_binary=).with("yum")
       pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "2.0.1.el5", "x86_64", [])
       expect(@yum_cache).to receive(:packages_from_require).and_return([pkg])
       @new_resource = Chef::Resource::YumPackage.new('test-package = 2.0.1.el5')
@@ -374,6 +443,7 @@ describe Chef::Provider::Package::Yum do
           :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+      allow(@yum_cache).to receive(:yum_binary=).with("yum")
 
       expect(@yum_cache).to receive(:packages_from_require).exactly(4).times.and_return([])
       expect(@yum_cache).to receive(:reload_provides).twice
@@ -398,6 +468,7 @@ describe Chef::Provider::Package::Yum do
         :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+      allow(@yum_cache).to receive(:yum_binary=).with("yum")
       expect(@yum_cache).to receive(:packages_from_require).twice.and_return([])
       expect(@yum_cache).to receive(:reload_provides)
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@@ -417,6 +488,7 @@ describe Chef::Provider::Package::Yum do
         :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+      allow(@yum_cache).to receive(:yum_binary=).with("yum")
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
       expect(@yum_cache).to receive(:packages_from_require).once.and_return([])
       expect(@yum_cache).not_to receive(:reload_provides)
@@ -441,6 +513,7 @@ describe Chef::Provider::Package::Yum do
         :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+      allow(@yum_cache).to receive(:yum_binary=).with("yum")
       expect(@yum_cache).to receive(:packages_from_require).twice.and_return([])
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
       @provider.load_current_resource
@@ -453,7 +526,7 @@ describe Chef::Provider::Package::Yum do
       @provider.load_current_resource
       allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y install cups-1.2.4-11.19.el5"
+        "-d0 -e0 -y install cups-1.2.4-11.19.el5"
       )
       @provider.install_package("cups", "1.2.4-11.19.el5")
     end
@@ -461,7 +534,7 @@ describe Chef::Provider::Package::Yum do
     it "should run yum localinstall if given a path to an rpm" do
       allow(@new_resource).to receive(:source).and_return("/tmp/emacs-21.4-20.el5.i386.rpm")
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
+        "-d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
       )
       @provider.install_package("emacs", "21.4-20.el5")
     end
@@ -472,7 +545,7 @@ describe Chef::Provider::Package::Yum do
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
       expect(@new_resource.source).to eq("/tmp/emacs-21.4-20.el5.i386.rpm")
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
+        "-d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
       )
       @provider.install_package("/tmp/emacs-21.4-20.el5.i386.rpm", "21.4-20.el5")
     end
@@ -482,7 +555,7 @@ describe Chef::Provider::Package::Yum do
       allow(@new_resource).to receive(:arch).and_return("i386")
       allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y install cups-1.2.4-11.19.el5.i386"
+        "-d0 -e0 -y install cups-1.2.4-11.19.el5.i386"
       )
       @provider.install_package("cups", "1.2.4-11.19.el5")
     end
@@ -493,7 +566,7 @@ describe Chef::Provider::Package::Yum do
       allow(@new_resource).to receive(:options).and_return("--disablerepo epmd")
       allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y --disablerepo epmd install cups-11"
+        "-d0 -e0 -y --disablerepo epmd install cups-11"
       )
       @provider.install_package(@new_resource.name, @provider.candidate_version)
     end
@@ -510,6 +583,7 @@ describe Chef::Provider::Package::Yum do
         :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+      allow(@yum_cache).to receive(:yum_binary=).with("yum")
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
       expect { @provider.install_package("lolcats", "0.99") }.to raise_error(Chef::Exceptions::Package, %r{Version .* not found})
     end
@@ -528,6 +602,7 @@ describe Chef::Provider::Package::Yum do
         :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+      allow(@yum_cache).to receive(:yum_binary=).with("yum")
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
       @provider.load_current_resource
       expect { @provider.install_package("cups", "1.2.4-11.15.el5") }.to raise_error(Chef::Exceptions::Package, %r{is newer than candidate package})
@@ -547,10 +622,11 @@ describe Chef::Provider::Package::Yum do
         :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+      allow(@yum_cache).to receive(:yum_binary=).with("yum")
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
       @provider.load_current_resource
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
+        "-d0 -e0 -y install cups-1.2.4-11.15.el5"
       )
       @provider.install_package("cups", "1.2.4-11.15.el5")
     end
@@ -570,10 +646,11 @@ describe Chef::Provider::Package::Yum do
         :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+      allow(@yum_cache).to receive(:yum_binary=).with("yum")
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
       @provider.load_current_resource
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y downgrade cups-1.2.4-11.15.el5"
+        "-d0 -e0 -y downgrade cups-1.2.4-11.15.el5"
       )
       @provider.install_package("cups", "1.2.4-11.15.el5")
     end
@@ -583,7 +660,7 @@ describe Chef::Provider::Package::Yum do
       @provider.load_current_resource
       allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
+        "-d0 -e0 -y install cups-1.2.4-11.15.el5"
       )
       expect(@yum_cache).to receive(:reload).once
       @provider.install_package("cups", "1.2.4-11.15.el5")
@@ -594,7 +671,7 @@ describe Chef::Provider::Package::Yum do
       @provider.load_current_resource
       allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
+        "-d0 -e0 -y install cups-1.2.4-11.15.el5"
       )
       expect(@yum_cache).not_to receive(:reload)
       @provider.install_package("cups", "1.2.4-11.15.el5")
@@ -607,7 +684,7 @@ describe Chef::Provider::Package::Yum do
       allow(@provider).to receive(:candidate_version).and_return('11')
       allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y install cups-11"
+        "-d0 -e0 -y install cups-11"
       )
       @provider.upgrade_package(@new_resource.name, @provider.candidate_version)
     end
@@ -618,7 +695,7 @@ describe Chef::Provider::Package::Yum do
       allow(@provider).to receive(:candidate_version).and_return('11')
       allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y install cups-11"
+        "-d0 -e0 -y install cups-11"
       )
       @provider.upgrade_package(@new_resource.name, @provider.candidate_version)
     end
@@ -636,6 +713,7 @@ describe Chef::Provider::Package::Yum do
         :disable_extra_repo_control => true
       )
       allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+      allow(@yum_cache).to receive(:yum_binary=).with("yum")
       @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
       @provider.load_current_resource
       expect { @provider.upgrade_package("cups", "1.2.4-11.15.el5") }.to raise_error(Chef::Exceptions::Package, %r{is newer than candidate package})
@@ -685,7 +763,7 @@ describe Chef::Provider::Package::Yum do
   describe "when removing a package" do
     it "should run yum remove with the package name" do
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y remove emacs-1.0"
+        "-d0 -e0 -y remove emacs-1.0"
       )
       @provider.remove_package("emacs", "1.0")
     end
@@ -693,7 +771,7 @@ describe Chef::Provider::Package::Yum do
     it "should run yum remove with the package name and arch" do
       allow(@new_resource).to receive(:arch).and_return("x86_64")
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y remove emacs-1.0.x86_64"
+        "-d0 -e0 -y remove emacs-1.0.x86_64"
       )
       @provider.remove_package("emacs", "1.0")
     end
@@ -702,7 +780,7 @@ describe Chef::Provider::Package::Yum do
   describe "when purging a package" do
     it "should run yum remove with the package name" do
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y remove emacs-1.0"
+        "-d0 -e0 -y remove emacs-1.0"
       )
       @provider.purge_package("emacs", "1.0")
     end
@@ -716,7 +794,7 @@ describe Chef::Provider::Package::Yum do
         "yum -d0 -e0 -y install emacs-1.0",
         {:timeout => Chef::Config[:yum_timeout]}
       )
-      @provider.yum_command("yum -d0 -e0 -y install emacs-1.0")
+      @provider.yum_command("-d0 -e0 -y install emacs-1.0")
     end
 
     it "should run yum once if it exits with a return code > 0 and no scriptlet failures" do
@@ -726,7 +804,7 @@ describe Chef::Provider::Package::Yum do
         "yum -d0 -e0 -y install emacs-1.0",
         {:timeout => Chef::Config[:yum_timeout]}
       )
-      expect { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+      expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
     end
 
     it "should run yum once if it exits with a return code of 1 and %pre scriptlet failures" do
@@ -738,7 +816,7 @@ describe Chef::Provider::Package::Yum do
         {:timeout => Chef::Config[:yum_timeout]}
       )
       # will still raise an exception, can't stub out the subsequent call
-      expect { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+      expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
     end
 
     it "should run yum twice if it exits with a return code of 1 and %post scriptlet failures" do
@@ -750,7 +828,20 @@ describe Chef::Provider::Package::Yum do
         {:timeout => Chef::Config[:yum_timeout]}
       )
       # will still raise an exception, can't stub out the subsequent call
-      expect { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+      expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+    end
+
+    it "should pass the yum_binary to the command if its specified" do
+      @new_resource.yum_binary "yum-deprecated"
+      expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated")
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @status = double("Status", :exitstatus => 0, :stdout => "", :stderr => "")
+      allow(@provider).to receive(:shell_out).and_return(@status)
+      expect(@provider).to receive(:shell_out).once.with(
+        "yum-deprecated -d0 -e0 -y install emacs-1.0",
+        {:timeout => Chef::Config[:yum_timeout]}
+      )
+      @provider.yum_command("-d0 -e0 -y install emacs-1.0")
     end
   end
 end
@@ -1659,6 +1750,14 @@ describe Chef::Provider::Package::Yum::YumCache do
     end
   end
 
+  let(:yum_exe) {
+    StringIO.new("#!/usr/bin/python\n\naldsjfa\ldsajflkdsjf\lajsdfj")
+  }
+
+  let(:bin_exe) {
+    StringIO.new(SecureRandom.random_bytes)
+  }
+
   before(:each) do
     @stdin = double("STDIN", :nil_object => true)
     @stdout = double("STDOUT", :nil_object => true)
@@ -1704,12 +1803,20 @@ file: file://///etc/yum.repos.d/CentOS-Base.repo, line: 12
 'qeqwewe\n'
 EOF
     @status = double("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_good, :stderr => @stderr)
-
     # new singleton each time
     Chef::Provider::Package::Yum::YumCache.reset_instance
     @yc = Chef::Provider::Package::Yum::YumCache.instance
     # load valid data
+    @yc.yum_binary = "yum"
     allow(@yc).to receive(:shell_out!).and_return(@status)
+    allow_any_instance_of(described_class).to receive(:which).with("yum").and_return("/usr/bin/yum")
+    allow(::File).to receive(:open).with("/usr/bin/yum", "r") do |&block|
+      res = block.call(yum_exe)
+      # a bit of a hack. rewind this since it seem that no matter what
+      # I do, we get the same StringIO objects on multiple calls to
+      # ::File.open
+      yum_exe.rewind; res
+    end
   end
 
   describe "initialize" do
@@ -1726,6 +1833,30 @@ EOF
     end
   end
 
+  describe "python_bin" do
+    it "should return the default python if an error occurs" do
+      allow(::File).to receive(:open).with("/usr/bin/yum", "r").and_raise(StandardError)
+      expect(@yc.python_bin).to eq("/usr/bin/python")
+    end
+
+    it "should return the default python if the yum-executable doesn't start with #!" do
+      allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(bin_exe); bin_exe.rewind; r}
+      expect(@yc.python_bin).to eq("/usr/bin/python")
+    end
+
+    it "should return /usr/bin/python if the interpreter is /bin/bash" do
+      other = StringIO.new("#!/bin/bash\n# The yum executable redirecting to dnf from dnf-yum compatible package.")
+      allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(other); other.rewind; r}
+      expect(@yc.python_bin).to eq("/usr/bin/python")
+    end
+
+    it "should return the interpreter for yum" do
+      other = StringIO.new("#!/usr/bin/super_python\n\nlasjdfdsaljf\nlasdjfs")
+      allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(other); other.rewind; r}
+      expect(@yc.python_bin).to eq("/usr/bin/super_python")
+    end
+  end
+
   describe "refresh" do
     it "should implicitly call yum-dump.py only once by default after being instantiated" do
       expect(@yc).to receive(:shell_out!).once
@@ -1999,6 +2130,8 @@ describe "Chef::Provider::Package::Yum - Multi" do
       :disable_extra_repo_control => true
     )
     allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+    allow(@yum_cache).to receive(:yum_binary=).with("yum")
+    allow(@yum_cache).to receive(:yum_binary=).with("yum")
     @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
     @pid = double("PID")
   end
@@ -2041,6 +2174,36 @@ describe "Chef::Provider::Package::Yum - Multi" do
     it "should return the current resouce" do
       expect(@provider.load_current_resource).to eql(@provider.current_resource)
     end
+
+    describe "when version constraint in package_name" do
+      it "should set package_version if no existing package_name is found and new_package_name is available" do
+        @new_resource = Chef::Resource::Package.new(['cups = 1.2.4-11.18.el5_2.3', 'emacs = 24.4'])
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        allow(@yum_cache).to receive(:package_available?) { |pkg| %w(cups emacs).include?(pkg) ? true : false }
+        allow(@yum_cache).to receive(:candidate_version) do |pkg|
+          if pkg == 'cups'
+            "1.2.4-11.18.el5_2.3"
+          elsif pkg == 'emacs'
+            "24.4"
+          end
+        end
+        allow(@yum_cache).to receive(:packages_from_require) do |pkg|
+          if pkg.name == 'cups'
+            [Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.3", "noarch", [], false, true, "base")]
+          elsif pkg.name == 'emacs'
+            [Chef::Provider::Package::Yum::RPMDbPackage.new("emacs", "24.4", "noarch", [], false, true, "base")]
+          end
+        end
+        expect(Chef::Log).to receive(:debug).exactly(2).times.with(%r{matched 1 package,})
+        expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{candidate version: \["1.2.4-11.18.el5_2.3", "24.4"\]})
+        expect(Chef::Log).to receive(:debug).at_least(2).times.with(%r{checking yum info})
+        @provider.load_current_resource
+        expect(@provider.new_resource.package_name).to eq(["cups", "emacs"])
+        expect(@provider.new_resource.version).to eq(["1.2.4-11.18.el5_2.3", "24.4"])
+        expect(@provider.send(:package_name_array)).to eq(["cups", "emacs"])
+        expect(@provider.send(:new_version_array)).to eq(["1.2.4-11.18.el5_2.3", "24.4"])
+      end
+    end
   end
 
   describe "when installing a package" do
@@ -2050,7 +2213,7 @@ describe "Chef::Provider::Package::Yum - Multi" do
       allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
       allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9')
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y install cups-1.2.4-11.19.el5 vim-1.0"
+        "-d0 -e0 -y install cups-1.2.4-11.19.el5 vim-1.0"
       )
       @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
     end
@@ -2060,7 +2223,7 @@ describe "Chef::Provider::Package::Yum - Multi" do
       allow(@new_resource).to receive(:arch).and_return("i386")
       allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y install cups-1.2.4-11.19.el5.i386 vim-1.0.i386"
+        "-d0 -e0 -y install cups-1.2.4-11.19.el5.i386 vim-1.0.i386"
       )
       @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", "1.0"])
     end
@@ -2071,10 +2234,36 @@ describe "Chef::Provider::Package::Yum - Multi" do
       allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
       allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9')
       expect(@provider).to receive(:yum_command).with(
-        "yum -d0 -e0 -y --disablerepo epmd install cups-1.2.4-11.19.el5 vim-1.0"
+        "-d0 -e0 -y --disablerepo epmd install cups-1.2.4-11.19.el5 vim-1.0"
       )
       allow(@new_resource).to receive(:options).and_return("--disablerepo epmd")
       @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
     end
+
+    it "should run yum install with the package name and version when name has arch" do
+      @new_resource = Chef::Resource::Package.new(['cups.x86_64', 'vim'])
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
+
+      # Inside of load_current_resource() we'll call parse_arch for cups,
+      # and we need to craft the right response. The default mock setup above
+      # will just return valid versions all the time which won't work for this
+      # test.
+      allow(@yum_cache).to receive(:installed_version).with('cups', 'x86_64').and_return('XXXX')
+      allow(@yum_cache).to receive(:candidate_version).with('cups', 'x86_64').and_return('1.2.4-11.18.el5')
+      allow(@yum_cache).to receive(:installed_version).with('cups.x86_64').and_return(nil)
+      allow(@yum_cache).to receive(:candidate_version).with('cups.x86_64').and_return(nil)
+
+      # Normal mock's for the idempotency check
+      allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
+      allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9')
+
+      @provider.load_current_resource
+      expect(@provider).to receive(:yum_command).with(
+        "-d0 -e0 -y install cups-1.2.4-11.19.el5.x86_64 vim-1.0"
+      )
+      @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
+    end
+
   end
 end
diff --git a/spec/unit/provider/package/zypper_spec.rb b/spec/unit/provider/package/zypper_spec.rb
index 706ad72..18ff739 100644
--- a/spec/unit/provider/package/zypper_spec.rb
+++ b/spec/unit/provider/package/zypper_spec.rb
@@ -19,126 +19,150 @@
 require 'spec_helper'
 
 describe Chef::Provider::Package::Zypper do
+  let!(:new_resource) { Chef::Resource::ZypperPackage.new("cups") }
+
+  let!(:current_resource) { Chef::Resource::ZypperPackage.new("cups") }
+
+  let(:provider) do
+    node = Chef::Node.new
+    events = Chef::EventDispatch::Dispatcher.new
+    run_context = Chef::RunContext.new(node, {}, events)
+    Chef::Provider::Package::Zypper.new(new_resource, run_context)
+  end
+
+  let(:status) { double(:stdout => "\n", :exitstatus => 0) }
+
   before(:each) do
-    @node = Chef::Node.new
-    @events = Chef::EventDispatch::Dispatcher.new
-    @run_context = Chef::RunContext.new(@node, {}, @events)
-    @new_resource = Chef::Resource::Package.new("cups")
-
-    @current_resource = Chef::Resource::Package.new("cups")
-
-    @provider = Chef::Provider::Package::Zypper.new(@new_resource, @run_context)
-    allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
-    @status = double(:stdout => "\n", :exitstatus => 0)
-    allow(@provider).to receive(:shell_out).and_return(@status)
-    allow(@provider).to receive(:`).and_return("2.0")
+    allow(Chef::Resource::Package).to receive(:new).and_return(current_resource)
+    allow(provider).to receive(:shell_out).and_return(status)
+    allow(provider).to receive(:`).and_return("2.0")
+  end
+
+  def shell_out_expectation(command, options=nil)
+    options ||= { timeout: 900 }
+    expect(provider).to receive(:shell_out).with(command, options)
+  end
+
+  def shell_out_expectation!(command, options=nil)
+    options ||= { timeout: 900 }
+    expect(provider).to receive(:shell_out!).with(command, options)
   end
 
   describe "when loading the current package state" do
     it "should create a current resource with the name of the new_resource" do
-      expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
-      @provider.load_current_resource
+      expect(Chef::Resource::Package).to receive(:new).with(new_resource.name).and_return(current_resource)
+      provider.load_current_resource
     end
 
     it "should set the current resources package name to the new resources package name" do
-      expect(@current_resource).to receive(:package_name).with(@new_resource.package_name)
-      @provider.load_current_resource
+      expect(current_resource).to receive(:package_name).with(new_resource.package_name)
+      provider.load_current_resource
     end
 
     it "should run zypper info with the package name" do
-      expect(@provider).to receive(:shell_out).with("zypper --non-interactive info #{@new_resource.package_name}").and_return(@status)
-      @provider.load_current_resource
+      shell_out_expectation(
+        "zypper --non-interactive info #{new_resource.package_name}"
+      ).and_return(status)
+      provider.load_current_resource
     end
 
     it "should set the installed version to nil on the current resource if zypper info installed version is (none)" do
-      allow(@provider).to receive(:shell_out).and_return(@status)
-      expect(@current_resource).to receive(:version).with(nil).and_return(true)
-      @provider.load_current_resource
+      allow(provider).to receive(:shell_out).and_return(status)
+      expect(current_resource).to receive(:version).with(nil).and_return(true)
+      provider.load_current_resource
     end
 
     it "should set the installed version if zypper info has one" do
       status = double(:stdout => "Version: 1.0\nInstalled: Yes\n", :exitstatus => 0)
 
-      allow(@provider).to receive(:shell_out).and_return(status)
-      expect(@current_resource).to receive(:version).with("1.0").and_return(true)
-      @provider.load_current_resource
+      allow(provider).to receive(:shell_out).and_return(status)
+      expect(current_resource).to receive(:version).with("1.0").and_return(true)
+      provider.load_current_resource
     end
 
     it "should set the candidate version if zypper info has one" do
       status = double(:stdout => "Version: 1.0\nInstalled: No\nStatus: out-of-date (version 0.9 installed)", :exitstatus => 0)
 
-      allow(@provider).to receive(:shell_out).and_return(status)
-      @provider.load_current_resource
-      expect(@provider.candidate_version).to eql("1.0")
+      allow(provider).to receive(:shell_out).and_return(status)
+      provider.load_current_resource
+      expect(provider.candidate_version).to eql("1.0")
     end
 
     it "should raise an exception if zypper info fails" do
-      expect(@status).to receive(:exitstatus).and_return(1)
-      expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package)
+      expect(status).to receive(:exitstatus).and_return(1)
+      expect { provider.load_current_resource }.to raise_error(Chef::Exceptions::Package)
     end
 
     it "should not raise an exception if zypper info succeeds" do
-      expect(@status).to receive(:exitstatus).and_return(0)
-      expect { @provider.load_current_resource }.not_to raise_error
+      expect(status).to receive(:exitstatus).and_return(0)
+      expect { provider.load_current_resource }.not_to raise_error
     end
 
     it "should return the current resouce" do
-      expect(@provider.load_current_resource).to eql(@current_resource)
+      expect(provider.load_current_resource).to eql(current_resource)
     end
   end
 
   describe "install_package" do
     it "should run zypper install with the package name and version" do
       allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true)
-      expect(@provider).to receive(:shell_out!).with(
-        "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0")
-      @provider.install_package("emacs", "1.0")
+      shell_out_expectation!(
+        "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0"
+      )
+      provider.install_package("emacs", "1.0")
     end
     it "should run zypper install without gpg checks" do
       allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false)
-      expect(@provider).to receive(:shell_out!).with(
+      shell_out_expectation!(
         "zypper --non-interactive --no-gpg-checks install "+
-        "--auto-agree-with-licenses emacs=1.0")
-      @provider.install_package("emacs", "1.0")
+        "--auto-agree-with-licenses emacs=1.0"
+      )
+      provider.install_package("emacs", "1.0")
     end
     it "should warn about gpg checks on zypper install" do
       expect(Chef::Log).to receive(:warn).with(
-        /All packages will be installed without gpg signature checks/)
-      expect(@provider).to receive(:shell_out!).with(
+        /All packages will be installed without gpg signature checks/
+      )
+      shell_out_expectation!(
         "zypper --non-interactive --no-gpg-checks install "+
-        "--auto-agree-with-licenses emacs=1.0")
-      @provider.install_package("emacs", "1.0")
+        "--auto-agree-with-licenses emacs=1.0"
+      )
+      provider.install_package("emacs", "1.0")
     end
   end
 
   describe "upgrade_package" do
     it "should run zypper update with the package name and version" do
       allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true)
-      expect(@provider).to receive(:shell_out!).with(
-        "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0")
-      @provider.upgrade_package("emacs", "1.0")
+      shell_out_expectation!(
+        "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0"
+      )
+      provider.upgrade_package("emacs", "1.0")
     end
     it "should run zypper update without gpg checks" do
       allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false)
-      expect(@provider).to receive(:shell_out!).with(
+      shell_out_expectation!(
         "zypper --non-interactive --no-gpg-checks install "+
-        "--auto-agree-with-licenses emacs=1.0")
-      @provider.upgrade_package("emacs", "1.0")
+        "--auto-agree-with-licenses emacs=1.0"
+      )
+      provider.upgrade_package("emacs", "1.0")
     end
     it "should warn about gpg checks on zypper upgrade" do
       expect(Chef::Log).to receive(:warn).with(
-        /All packages will be installed without gpg signature checks/)
-      expect(@provider).to receive(:shell_out!).with(
+        /All packages will be installed without gpg signature checks/
+      )
+      shell_out_expectation!(
         "zypper --non-interactive --no-gpg-checks install "+
-        "--auto-agree-with-licenses emacs=1.0")
-      @provider.upgrade_package("emacs", "1.0")
+        "--auto-agree-with-licenses emacs=1.0"
+      )
+      provider.upgrade_package("emacs", "1.0")
     end
     it "should run zypper upgrade without gpg checks" do
-      expect(@provider).to receive(:shell_out!).with(
+      shell_out_expectation!(
         "zypper --non-interactive --no-gpg-checks install "+
-        "--auto-agree-with-licenses emacs=1.0")
-
-      @provider.upgrade_package("emacs", "1.0")
+        "--auto-agree-with-licenses emacs=1.0"
+      )
+      provider.upgrade_package("emacs", "1.0")
     end
   end
 
@@ -147,83 +171,94 @@ describe Chef::Provider::Package::Zypper do
     context "when package version is not explicitly specified" do
       it "should run zypper remove with the package name" do
         allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true)
-        expect(@provider).to receive(:shell_out!).with(
-            "zypper --non-interactive remove emacs")
-        @provider.remove_package("emacs", nil)
+        shell_out_expectation!(
+            "zypper --non-interactive remove emacs"
+        )
+        provider.remove_package("emacs", nil)
       end
     end
 
     context "when package version is explicitly specified" do
       it "should run zypper remove with the package name" do
         allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true)
-        expect(@provider).to receive(:shell_out!).with(
-          "zypper --non-interactive remove emacs=1.0")
-        @provider.remove_package("emacs", "1.0")
+        shell_out_expectation!(
+          "zypper --non-interactive remove emacs=1.0"
+        )
+        provider.remove_package("emacs", "1.0")
       end
       it "should run zypper remove without gpg checks" do
         allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false)
-        expect(@provider).to receive(:shell_out!).with(
-            "zypper --non-interactive --no-gpg-checks remove emacs=1.0")
-        @provider.remove_package("emacs", "1.0")
+        shell_out_expectation!(
+            "zypper --non-interactive --no-gpg-checks remove emacs=1.0"
+        )
+        provider.remove_package("emacs", "1.0")
       end
       it "should warn about gpg checks on zypper remove" do
         expect(Chef::Log).to receive(:warn).with(
-          /All packages will be installed without gpg signature checks/)
-        expect(@provider).to receive(:shell_out!).with(
-          "zypper --non-interactive --no-gpg-checks remove emacs=1.0")
-
-        @provider.remove_package("emacs", "1.0")
+          /All packages will be installed without gpg signature checks/
+        )
+        shell_out_expectation!(
+          "zypper --non-interactive --no-gpg-checks remove emacs=1.0"
+        )
+        provider.remove_package("emacs", "1.0")
       end
     end
   end
 
   describe "purge_package" do
     it "should run remove_package with the name and version" do
-      expect(@provider).to receive(:shell_out!).with(
-        "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0")
-      @provider.purge_package("emacs", "1.0")
+      shell_out_expectation!(
+        "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0"
+      )
+      provider.purge_package("emacs", "1.0")
     end
     it "should run zypper purge without gpg checks" do
       allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false)
-      expect(@provider).to receive(:shell_out!).with(
-        "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0")
-      @provider.purge_package("emacs", "1.0")
+      shell_out_expectation!(
+        "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0"
+      )
+      provider.purge_package("emacs", "1.0")
     end
     it "should warn about gpg checks on zypper purge" do
       expect(Chef::Log).to receive(:warn).with(
-        /All packages will be installed without gpg signature checks/)
-      expect(@provider).to receive(:shell_out!).with(
-        "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0")
-      @provider.purge_package("emacs", "1.0")
+        /All packages will be installed without gpg signature checks/
+      )
+      shell_out_expectation!(
+        "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0"
+      )
+      provider.purge_package("emacs", "1.0")
     end
   end
 
   describe "on an older zypper" do
     before(:each) do
-      allow(@provider).to receive(:`).and_return("0.11.6")
+      allow(provider).to receive(:`).and_return("0.11.6")
     end
 
     describe "install_package" do
       it "should run zypper install with the package name and version" do
-        expect(@provider).to receive(:shell_out!).with(
-          "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs")
-        @provider.install_package("emacs", "1.0")
+        shell_out_expectation!(
+          "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs"
+        )
+        provider.install_package("emacs", "1.0")
       end
     end
 
     describe "upgrade_package" do
       it "should run zypper update with the package name and version" do
-        expect(@provider).to receive(:shell_out!).with(
-          "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs")
-        @provider.upgrade_package("emacs", "1.0")
+        shell_out_expectation!(
+          "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs"
+        )
+        provider.upgrade_package("emacs", "1.0")
       end
     end
 
     describe "remove_package" do
       it "should run zypper remove with the package name" do
-        expect(@provider).to receive(:shell_out!).with(
-           "zypper --no-gpg-checks remove -y emacs")
-        @provider.remove_package("emacs", "1.0")
+        shell_out_expectation!(
+           "zypper --no-gpg-checks remove -y emacs"
+        )
+        provider.remove_package("emacs", "1.0")
       end
     end
   end
diff --git a/spec/unit/provider/package_spec.rb b/spec/unit/provider/package_spec.rb
index 1633d18..f77c2b5 100644
--- a/spec/unit/provider/package_spec.rb
+++ b/spec/unit/provider/package_spec.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,341 +19,351 @@
 require 'spec_helper'
 
 describe Chef::Provider::Package do
-  before do
-    @node = Chef::Node.new
-    @events = Chef::EventDispatch::Dispatcher.new
-    @run_context = Chef::RunContext.new(@node, {}, @events)
-    @new_resource = Chef::Resource::Package.new('emacs')
-    @current_resource = Chef::Resource::Package.new('emacs')
-    @provider = Chef::Provider::Package.new(@new_resource, @run_context)
-    @provider.current_resource = @current_resource
-
-    @provider.candidate_version = "1.0"
+  let(:node) do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = :just_testing
+    node.automatic_attrs[:platform_version] = :just_testing
+    node
+  end
+  let(:events) { Chef::EventDispatch::Dispatcher.new }
+  let(:run_context) { Chef::RunContext.new(node, {}, events) }
+  let(:new_resource) { Chef::Resource::Package.new('emacs') }
+  let(:current_resource) { Chef::Resource::Package.new('emacs') }
+  let(:candidate_version) { "1.0" }
+  let(:provider) do
+    provider = Chef::Provider::Package.new(new_resource, run_context)
+    provider.current_resource = current_resource
+    provider.candidate_version = candidate_version
+    provider
   end
 
   describe "when installing a package" do
     before(:each) do
-      @provider.current_resource = @current_resource
-      allow(@provider).to receive(:install_package).and_return(true)
+      provider.current_resource = current_resource
+      allow(provider).to receive(:install_package).and_return(true)
+    end
+
+    it "raises a Chef::Exceptions::InvalidResourceSpecification if both multipackage and source are provided" do
+      new_resource.package_name(['a', 'b'])
+      new_resource.source('foo')
+      expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::InvalidResourceSpecification)
     end
 
     it "should raise a Chef::Exceptions::Package if no version is specified, and no candidate is available" do
-      @provider.candidate_version = nil
-      expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+      provider.candidate_version = nil
+      expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
     end
 
     it "should call preseed_package if a response_file is given" do
-      @new_resource.response_file("foo")
-      expect(@provider).to receive(:get_preseed_file).with(
-        @new_resource.name,
-        @provider.candidate_version
+      new_resource.response_file("foo")
+      expect(provider).to receive(:get_preseed_file).with(
+        new_resource.name,
+        provider.candidate_version
       ).and_return("/var/cache/preseed-test")
 
-      expect(@provider).to receive(:preseed_package).with(
+      expect(provider).to receive(:preseed_package).with(
         "/var/cache/preseed-test"
       ).and_return(true)
-      @provider.run_action(:install)
+      provider.run_action(:install)
     end
 
     it "should not call preseed_package if a response_file is not given" do
-      expect(@provider).not_to receive(:preseed_package)
-      @provider.run_action(:install)
+      expect(provider).not_to receive(:preseed_package)
+      provider.run_action(:install)
     end
 
     it "should install the package at the candidate_version if it is not already installed" do
-      expect(@provider).to receive(:install_package).with(
-        @new_resource.name,
-        @provider.candidate_version
+      expect(provider).to receive(:install_package).with(
+        new_resource.name,
+        provider.candidate_version
       ).and_return(true)
-      @provider.run_action(:install)
-      expect(@new_resource).to be_updated_by_last_action
+      provider.run_action(:install)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should install the package at the version specified if it is not already installed" do
-      @new_resource.version("1.0")
-      expect(@provider).to receive(:install_package).with(
-        @new_resource.name,
-        @new_resource.version
+      new_resource.version("1.0")
+      expect(provider).to receive(:install_package).with(
+        new_resource.name,
+        new_resource.version
       ).and_return(true)
-      @provider.run_action(:install)
-      expect(@new_resource).to be_updated_by_last_action
+      provider.run_action(:install)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should install the package at the version specified if a different version is installed" do
-      @new_resource.version("1.0")
-      allow(@current_resource).to receive(:version).and_return("0.99")
-      expect(@provider).to receive(:install_package).with(
-        @new_resource.name,
-        @new_resource.version
+      new_resource.version("1.0")
+      allow(current_resource).to receive(:version).and_return("0.99")
+      expect(provider).to receive(:install_package).with(
+        new_resource.name,
+        new_resource.version
       ).and_return(true)
-      @provider.run_action(:install)
-      expect(@new_resource).to be_updated_by_last_action
+      provider.run_action(:install)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should not install the package if it is already installed and no version is specified" do
-      @current_resource.version("1.0")
-      expect(@provider).not_to receive(:install_package)
-      @provider.run_action(:install)
-      expect(@new_resource).not_to be_updated_by_last_action
+      current_resource.version("1.0")
+      expect(provider).not_to receive(:install_package)
+      provider.run_action(:install)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should not install the package if it is already installed at the version specified" do
-      @current_resource.version("1.0")
-      @new_resource.version("1.0")
-      expect(@provider).not_to receive(:install_package)
-      @provider.run_action(:install)
-      expect(@new_resource).not_to be_updated_by_last_action
+      current_resource.version("1.0")
+      new_resource.version("1.0")
+      expect(provider).not_to receive(:install_package)
+      provider.run_action(:install)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should call the candidate_version accessor only once if the package is already installed and no version is specified" do
-      @current_resource.version("1.0")
-      allow(@provider).to receive(:candidate_version).and_return("1.0")
-      @provider.run_action(:install)
+      current_resource.version("1.0")
+      allow(provider).to receive(:candidate_version).and_return("1.0")
+      provider.run_action(:install)
     end
 
     it "should call the candidate_version accessor only once if the package is already installed at the version specified" do
-      @current_resource.version("1.0")
-      @new_resource.version("1.0")
-      @provider.run_action(:install)
+      current_resource.version("1.0")
+      new_resource.version("1.0")
+      provider.run_action(:install)
     end
 
     it "should set the resource to updated if it installs the package" do
-      @provider.run_action(:install)
-      expect(@new_resource).to be_updated
+      provider.run_action(:install)
+      expect(new_resource).to be_updated
     end
 
   end
 
   describe "when upgrading the package" do
     before(:each) do
-      allow(@provider).to receive(:upgrade_package).and_return(true)
+      allow(provider).to receive(:upgrade_package).and_return(true)
     end
 
     it "should upgrade the package if the current version is not the candidate version" do
-      expect(@provider).to receive(:upgrade_package).with(
-        @new_resource.name,
-        @provider.candidate_version
+      expect(provider).to receive(:upgrade_package).with(
+        new_resource.name,
+        provider.candidate_version
       ).and_return(true)
-      @provider.run_action(:upgrade)
-      expect(@new_resource).to be_updated_by_last_action
+      provider.run_action(:upgrade)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should set the resource to updated if it installs the package" do
-      @provider.run_action(:upgrade)
-      expect(@new_resource).to be_updated
+      provider.run_action(:upgrade)
+      expect(new_resource).to be_updated
     end
 
     it "should not install the package if the current version is the candidate version" do
-      @current_resource.version "1.0"
-      expect(@provider).not_to receive(:upgrade_package)
-      @provider.run_action(:upgrade)
-      expect(@new_resource).not_to be_updated_by_last_action
+      current_resource.version "1.0"
+      expect(provider).not_to receive(:upgrade_package)
+      provider.run_action(:upgrade)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should print the word 'uninstalled' if there was no original version" do
-      allow(@current_resource).to receive(:version).and_return(nil)
+      allow(current_resource).to receive(:version).and_return(nil)
       expect(Chef::Log).to receive(:info).with("package[emacs] upgraded emacs to 1.0")
-      @provider.run_action(:upgrade)
-      expect(@new_resource).to be_updated_by_last_action
+      provider.run_action(:upgrade)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should raise a Chef::Exceptions::Package if current version and candidate are nil" do
-      allow(@current_resource).to receive(:version).and_return(nil)
-      @provider.candidate_version = nil
-      expect { @provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
+      allow(current_resource).to receive(:version).and_return(nil)
+      provider.candidate_version = nil
+      expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
     end
 
     it "should not install the package if candidate version is nil" do
-      @current_resource.version "1.0"
-      @provider.candidate_version = nil
-      expect(@provider).not_to receive(:upgrade_package)
-      @provider.run_action(:upgrade)
-      expect(@new_resource).not_to be_updated_by_last_action
+      current_resource.version "1.0"
+      provider.candidate_version = nil
+      expect(provider).not_to receive(:upgrade_package)
+      provider.run_action(:upgrade)
+      expect(new_resource).not_to be_updated_by_last_action
     end
   end
 
   describe "When removing the package" do
     before(:each) do
-      allow(@provider).to receive(:remove_package).and_return(true)
-      @current_resource.version '1.4.2'
+      allow(provider).to receive(:remove_package).and_return(true)
+      current_resource.version '1.4.2'
     end
 
     it "should remove the package if it is installed" do
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:remove_package).with('emacs', nil)
-      @provider.run_action(:remove)
-      expect(@new_resource).to be_updated
-      expect(@new_resource).to be_updated_by_last_action
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:remove_package).with('emacs', nil)
+      provider.run_action(:remove)
+      expect(new_resource).to be_updated
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should remove the package at a specific version if it is installed at that version" do
-      @new_resource.version "1.4.2"
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:remove_package).with('emacs', '1.4.2')
-      @provider.run_action(:remove)
-      expect(@new_resource).to be_updated_by_last_action
+      new_resource.version "1.4.2"
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:remove_package).with('emacs', '1.4.2')
+      provider.run_action(:remove)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should not remove the package at a specific version if it is not installed at that version" do
-      @new_resource.version "1.0"
-      expect(@provider).not_to be_removing_package
-      expect(@provider).not_to receive(:remove_package)
-      @provider.run_action(:remove)
-      expect(@new_resource).not_to be_updated_by_last_action
+      new_resource.version "1.0"
+      expect(provider).not_to be_removing_package
+      expect(provider).not_to receive(:remove_package)
+      provider.run_action(:remove)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should not remove the package if it is not installed" do
-      expect(@provider).not_to receive(:remove_package)
-      allow(@current_resource).to receive(:version).and_return(nil)
-      @provider.run_action(:remove)
-      expect(@new_resource).not_to be_updated_by_last_action
+      expect(provider).not_to receive(:remove_package)
+      allow(current_resource).to receive(:version).and_return(nil)
+      provider.run_action(:remove)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should set the resource to updated if it removes the package" do
-      @provider.run_action(:remove)
-      expect(@new_resource).to be_updated
+      provider.run_action(:remove)
+      expect(new_resource).to be_updated
     end
 
   end
 
   describe "When purging the package" do
     before(:each) do
-      allow(@provider).to receive(:purge_package).and_return(true)
-      @current_resource.version '1.4.2'
+      allow(provider).to receive(:purge_package).and_return(true)
+      current_resource.version '1.4.2'
     end
 
     it "should purge the package if it is installed" do
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:purge_package).with('emacs', nil)
-      @provider.run_action(:purge)
-      expect(@new_resource).to be_updated
-      expect(@new_resource).to be_updated_by_last_action
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:purge_package).with('emacs', nil)
+      provider.run_action(:purge)
+      expect(new_resource).to be_updated
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should purge the package at a specific version if it is installed at that version" do
-      @new_resource.version "1.4.2"
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:purge_package).with('emacs', '1.4.2')
-      @provider.run_action(:purge)
-      expect(@new_resource).to be_updated_by_last_action
+      new_resource.version "1.4.2"
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:purge_package).with('emacs', '1.4.2')
+      provider.run_action(:purge)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should not purge the package at a specific version if it is not installed at that version" do
-      @new_resource.version "1.0"
-      expect(@provider).not_to be_removing_package
-      expect(@provider).not_to receive(:purge_package)
-      @provider.run_action(:purge)
-      expect(@new_resource).not_to be_updated_by_last_action
+      new_resource.version "1.0"
+      expect(provider).not_to be_removing_package
+      expect(provider).not_to receive(:purge_package)
+      provider.run_action(:purge)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should not purge the package if it is not installed" do
-      @current_resource.instance_variable_set(:@version, nil)
-      expect(@provider).not_to be_removing_package
+      current_resource.instance_variable_set(:@version, nil)
+      expect(provider).not_to be_removing_package
 
-      expect(@provider).not_to receive(:purge_package)
-      @provider.run_action(:purge)
-      expect(@new_resource).not_to be_updated_by_last_action
+      expect(provider).not_to receive(:purge_package)
+      provider.run_action(:purge)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should set the resource to updated if it purges the package" do
-      @provider.run_action(:purge)
-      expect(@new_resource).to be_updated
+      provider.run_action(:purge)
+      expect(new_resource).to be_updated
     end
 
   end
 
   describe "when reconfiguring the package" do
     before(:each) do
-      allow(@provider).to receive(:reconfig_package).and_return(true)
+      allow(provider).to receive(:reconfig_package).and_return(true)
     end
 
     it "should info log, reconfigure the package and update the resource" do
-      allow(@current_resource).to receive(:version).and_return('1.0')
-      allow(@new_resource).to receive(:response_file).and_return(true)
-      expect(@provider).to receive(:get_preseed_file).and_return('/var/cache/preseed-test')
-      allow(@provider).to receive(:preseed_package).and_return(true)
-      allow(@provider).to receive(:reconfig_package).and_return(true)
+      allow(current_resource).to receive(:version).and_return('1.0')
+      allow(new_resource).to receive(:response_file).and_return(true)
+      expect(provider).to receive(:get_preseed_file).and_return('/var/cache/preseed-test')
+      allow(provider).to receive(:preseed_package).and_return(true)
+      allow(provider).to receive(:reconfig_package).and_return(true)
       expect(Chef::Log).to receive(:info).with("package[emacs] reconfigured")
-      expect(@provider).to receive(:reconfig_package)
-      @provider.run_action(:reconfig)
-      expect(@new_resource).to be_updated
-      expect(@new_resource).to be_updated_by_last_action
+      expect(provider).to receive(:reconfig_package)
+      provider.run_action(:reconfig)
+      expect(new_resource).to be_updated
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should debug log and not reconfigure the package if the package is not installed" do
-      allow(@current_resource).to receive(:version).and_return(nil)
+      allow(current_resource).to receive(:version).and_return(nil)
       expect(Chef::Log).to receive(:debug).with("package[emacs] is NOT installed - nothing to do")
-      expect(@provider).not_to receive(:reconfig_package)
-      @provider.run_action(:reconfig)
-      expect(@new_resource).not_to be_updated_by_last_action
+      expect(provider).not_to receive(:reconfig_package)
+      provider.run_action(:reconfig)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should debug log and not reconfigure the package if no response_file is given" do
-      allow(@current_resource).to receive(:version).and_return('1.0')
-      allow(@new_resource).to receive(:response_file).and_return(nil)
+      allow(current_resource).to receive(:version).and_return('1.0')
+      allow(new_resource).to receive(:response_file).and_return(nil)
       expect(Chef::Log).to receive(:debug).with("package[emacs] no response_file provided - nothing to do")
-      expect(@provider).not_to receive(:reconfig_package)
-      @provider.run_action(:reconfig)
-      expect(@new_resource).not_to be_updated_by_last_action
+      expect(provider).not_to receive(:reconfig_package)
+      provider.run_action(:reconfig)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should debug log and not reconfigure the package if the response_file has not changed" do
-      allow(@current_resource).to receive(:version).and_return('1.0')
-      allow(@new_resource).to receive(:response_file).and_return(true)
-      expect(@provider).to receive(:get_preseed_file).and_return(false)
-      allow(@provider).to receive(:preseed_package).and_return(false)
+      allow(current_resource).to receive(:version).and_return('1.0')
+      allow(new_resource).to receive(:response_file).and_return(true)
+      expect(provider).to receive(:get_preseed_file).and_return(false)
+      allow(provider).to receive(:preseed_package).and_return(false)
       expect(Chef::Log).to receive(:debug).with("package[emacs] preseeding has not changed - nothing to do")
-      expect(@provider).not_to receive(:reconfig_package)
-      @provider.run_action(:reconfig)
-      expect(@new_resource).not_to be_updated_by_last_action
+      expect(provider).not_to receive(:reconfig_package)
+      provider.run_action(:reconfig)
+      expect(new_resource).not_to be_updated_by_last_action
     end
   end
 
   describe "when running commands to be implemented by subclasses" do
     it "should raises UnsupportedAction for install" do
-      expect { @provider.install_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
+      expect { provider.install_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
     end
 
     it "should raises UnsupportedAction for upgrade" do
-      expect { @provider.upgrade_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
+      expect { provider.upgrade_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
     end
 
     it "should raises UnsupportedAction for remove" do
-      expect { @provider.remove_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
+      expect { provider.remove_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
     end
 
     it "should raises UnsupportedAction for purge" do
-      expect { @provider.purge_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
+      expect { provider.purge_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
     end
 
     it "should raise UnsupportedAction for preseed_package" do
       preseed_file = "/tmp/sun-jdk-package-preseed-file.seed"
-      expect { @provider.preseed_package(preseed_file) }.to raise_error(Chef::Exceptions::UnsupportedAction)
+      expect { provider.preseed_package(preseed_file) }.to raise_error(Chef::Exceptions::UnsupportedAction)
     end
 
     it "should raise UnsupportedAction for reconfig" do
-      expect { @provider.reconfig_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
+      expect { provider.reconfig_package('emacs', '1.4.2') }.to raise_error(Chef::Exceptions::UnsupportedAction)
     end
   end
 
   describe "when given a response file" do
-    before(:each) do
-      @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
-      Chef::Cookbook::FileVendor.fetch_from_disk(@cookbook_repo)
-
-      @node = Chef::Node.new
-      cl = Chef::CookbookLoader.new(@cookbook_repo)
-      cl.load_cookbooks
-      @cookbook_collection = Chef::CookbookCollection.new(cl)
-
-      @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
-      @provider.run_context = @run_context
-
-      @node.automatic_attrs[:platform] = 'PLATFORM: just testing'
-      @node.automatic_attrs[:platform_version] = 'PLATFORM VERSION: just testing'
-
-      @new_resource.response_file('java.response')
-      @new_resource.cookbook_name = 'java'
+    let(:cookbook_repo) { File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) }
+    let(:cookbook_loader) do
+      Chef::Cookbook::FileVendor.fetch_from_disk(cookbook_repo)
+      Chef::CookbookLoader.new(cookbook_repo)
+    end
+    let(:cookbook_collection) do
+      cookbook_loader.load_cookbooks
+      Chef::CookbookCollection.new(cookbook_loader)
+    end
+    let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) }
+    let(:new_resource) do
+      new_resource = Chef::Resource::Package.new('emacs')
+      new_resource.response_file('java.response')
+      new_resource.cookbook_name = 'java'
+      new_resource
     end
 
     describe "creating the cookbook file resource to fetch the response file" do
@@ -363,62 +373,60 @@ describe Chef::Provider::Package do
 
       it "sets the preseed resource's runcontext to its own run context" do
         allow(Chef::FileCache).to receive(:create_cache_path).and_return("/tmp/preseed/java")
-        expect(@provider.preseed_resource('java', '6').run_context).not_to be_nil
-        expect(@provider.preseed_resource('java', '6').run_context).to equal(@provider.run_context)
+        expect(provider.preseed_resource('java', '6').run_context).not_to be_nil
+        expect(provider.preseed_resource('java', '6').run_context).to equal(provider.run_context)
       end
 
       it "should set the cookbook name of the remote file to the new resources cookbook name" do
-        expect(@provider.preseed_resource('java', '6').cookbook_name).to eq('java')
+        expect(provider.preseed_resource('java', '6').cookbook_name).to eq('java')
       end
 
       it "should set remote files source to the new resources response file" do
-        expect(@provider.preseed_resource('java', '6').source).to eq('java.response')
+        expect(provider.preseed_resource('java', '6').source).to eq('java.response')
       end
 
       it "should never back up the cached response file" do
-        expect(@provider.preseed_resource('java', '6').backup).to be_falsey
+        expect(provider.preseed_resource('java', '6').backup).to be_falsey
       end
 
       it "sets the install path of the resource to $file_cache/$cookbook/$pkg_name-$pkg_version.seed" do
-        expect(@provider.preseed_resource('java', '6').path).to eq('/tmp/preseed/java/java-6.seed')
+        expect(provider.preseed_resource('java', '6').path).to eq('/tmp/preseed/java/java-6.seed')
       end
     end
 
     describe "when installing the preseed file to the cache location" do
-      before do
-        @node.automatic_attrs[:platform] = :just_testing
-        @node.automatic_attrs[:platform_version] = :just_testing
+      let(:response_file_destination) { Dir.tmpdir + '/preseed--java--java-6.seed' }
+      let(:response_file_resource) {
+        response_file_resource = Chef::Resource::CookbookFile.new(response_file_destination, run_context)
+        response_file_resource.cookbook_name = 'java'
+        response_file_resource.backup(false)
+        response_file_resource.source('java.response')
+        response_file_resource
+      }
 
-        @response_file_destination = Dir.tmpdir + '/preseed--java--java-6.seed'
-
-        @response_file_resource = Chef::Resource::CookbookFile.new(@response_file_destination, @run_context)
-        @response_file_resource.cookbook_name = 'java'
-        @response_file_resource.backup(false)
-        @response_file_resource.source('java.response')
-
-
-        expect(@provider).to receive(:preseed_resource).with('java', '6').and_return(@response_file_resource)
+      before do
+        expect(provider).to receive(:preseed_resource).with('java', '6').and_return(response_file_resource)
       end
 
       after do
-        FileUtils.rm(@response_file_destination) if ::File.exist?(@response_file_destination)
+        FileUtils.rm(response_file_destination) if ::File.exist?(response_file_destination)
       end
 
       it "creates the preseed file in the cache" do
-        expect(@response_file_resource).to receive(:run_action).with(:create)
-        @provider.get_preseed_file("java", "6")
+        expect(response_file_resource).to receive(:run_action).with(:create)
+        provider.get_preseed_file("java", "6")
       end
 
       it "returns the path to the response file if the response file was updated" do
-        expect(@provider.get_preseed_file("java", "6")).to eq(@response_file_destination)
+        expect(provider.get_preseed_file("java", "6")).to eq(response_file_destination)
       end
 
       it "should return false if the response file has not been updated" do
-        @response_file_resource.updated_by_last_action(false)
-        expect(@response_file_resource).not_to be_updated_by_last_action
+        response_file_resource.updated_by_last_action(false)
+        expect(response_file_resource).not_to be_updated_by_last_action
         # don't let the response_file_resource set updated to true
-        expect(@response_file_resource).to receive(:run_action).with(:create)
-        expect(@provider.get_preseed_file("java", "6")).to be(false)
+        expect(response_file_resource).to receive(:run_action).with(:create)
+        expect(provider.get_preseed_file("java", "6")).to be(false)
       end
 
     end
@@ -426,276 +434,437 @@ describe Chef::Provider::Package do
   end
 end
 
+describe "Subclass with use_multipackage_api" do
+  class MyPackageResource < Chef::Resource::Package
+  end
+
+  class MyPackageProvider < Chef::Provider::Package
+    use_multipackage_api
+  end
+
+  let(:node) { Chef::Node.new }
+  let(:events) { Chef::EventDispatch::Dispatcher.new }
+  let(:run_context) { Chef::RunContext.new(node, {}, events) }
+  let(:new_resource) { MyPackageResource.new("installs the packages") }
+  let(:current_resource) { MyPackageResource.new("installs the packages") }
+  let(:provider) do
+    provider = MyPackageProvider.new(new_resource, run_context)
+    provider.current_resource = current_resource
+    provider
+  end
+
+  it "has use_multipackage_api? methods on the class and instance" do
+    expect(MyPackageProvider.use_multipackage_api?).to be true
+    expect(provider.use_multipackage_api?).to be true
+  end
+
+  it "offers a_to_s to subclasses to convert an array of strings to a single string" do
+    expect(provider.send(:a_to_s, "a", nil, "b", "", "c", " ", "d e", "f-g")).to eq("a b c   d e f-g")
+  end
+
+  it "when user passes string to package_name, passes arrays to install_package" do
+    new_resource.package_name "vim"
+    new_resource.version nil
+    provider.candidate_version = [ "1.0" ]
+    expect(provider).to receive(:install_package).with(
+      [ "vim" ],
+      [ "1.0" ]
+    ).and_return(true)
+    provider.run_action(:install)
+    expect(new_resource).to be_updated_by_last_action
+    expect(new_resource.version).to eql(nil)
+  end
+
+  it "when user pases string to package_name and version, passes arrays to install_package" do
+    new_resource.package_name "vim"
+    new_resource.version "1.0"
+    provider.candidate_version = [ "1.0" ]
+    expect(provider).to receive(:install_package).with(
+      [ "vim" ],
+      [ "1.0" ]
+    ).and_return(true)
+    provider.run_action(:install)
+    expect(new_resource).to be_updated_by_last_action
+    expect(new_resource.version).to eql("1.0")
+  end
+
+  it "when user passes string to package_name, passes arrays to upgrade_package" do
+    new_resource.package_name "vim"
+    new_resource.version nil
+    provider.candidate_version = [ "1.0" ]
+    expect(provider).to receive(:upgrade_package).with(
+      [ "vim" ],
+      [ "1.0" ]
+    ).and_return(true)
+    provider.run_action(:upgrade)
+    expect(new_resource).to be_updated_by_last_action
+    expect(new_resource.version).to eql(nil)
+  end
+
+  it "when user pases string to package_name and version, passes arrays to upgrade_package" do
+    new_resource.package_name "vim"
+    new_resource.version "1.0"
+    provider.candidate_version = [ "1.0" ]
+    expect(provider).to receive(:upgrade_package).with(
+      [ "vim" ],
+      [ "1.0" ]
+    ).and_return(true)
+    provider.run_action(:upgrade)
+    expect(new_resource).to be_updated_by_last_action
+    expect(new_resource.version).to eql("1.0")
+  end
+
+  it "when user passes string to package_name, passes arrays to remove_package" do
+    new_resource.package_name "vim"
+    current_resource.package_name "vim"
+    current_resource.version [ "1.0" ]
+    provider.candidate_version = [ "1.0" ]
+    expect(provider).to receive(:remove_package).with(
+      [ "vim" ],
+      [ nil ]
+    ).and_return(true)
+    provider.run_action(:remove)
+    expect(new_resource).to be_updated_by_last_action
+    expect(new_resource.version).to eql(nil)
+  end
+
+  it "when user passes string to package_name, passes arrays to purge_package" do
+    new_resource.package_name "vim"
+    current_resource.package_name "vim"
+    current_resource.version [ "1.0" ]
+    provider.candidate_version = [ "1.0" ]
+    expect(provider).to receive(:purge_package).with(
+      [ "vim" ],
+      [ nil ]
+    ).and_return(true)
+    provider.run_action(:purge)
+    expect(new_resource).to be_updated_by_last_action
+    expect(new_resource.version).to eql(nil)
+  end
+
+  it "when user passes string to package_name, passes arrays to reconfig_package" do
+    new_resource.package_name "vim"
+    current_resource.package_name "vim"
+    current_resource.version [ "1.0" ]
+    allow(new_resource).to receive(:response_file).and_return(true)
+    expect(provider).to receive(:get_preseed_file).and_return('/var/cache/preseed-test')
+    allow(provider).to receive(:preseed_package).and_return(true)
+    allow(provider).to receive(:reconfig_package).and_return(true)
+    expect(provider).to receive(:reconfig_package).with(
+      [ "vim" ],
+      [ "1.0" ],
+    ).and_return(true)
+    provider.run_action(:reconfig)
+    expect(new_resource).to be_updated_by_last_action
+  end
+end
+
 describe "Chef::Provider::Package - Multi" do
-  before do
-    @node = Chef::Node.new
-    @events = Chef::EventDispatch::Dispatcher.new
-    @run_context = Chef::RunContext.new(@node, {}, @events)
-    @new_resource = Chef::Resource::Package.new(['emacs', 'vi'])
-    @current_resource = Chef::Resource::Package.new(['emacs', 'vi'])
-    @provider = Chef::Provider::Package.new(@new_resource, @run_context)
-    @provider.current_resource = @current_resource
-    @provider.candidate_version = ['1.0', '6.2']
+  let(:node) { Chef::Node.new }
+  let(:events) { Chef::EventDispatch::Dispatcher.new }
+  let(:run_context) { Chef::RunContext.new(node, {}, events) }
+  let(:new_resource) { Chef::Resource::Package.new(['emacs', 'vi']) }
+  let(:current_resource) { Chef::Resource::Package.new(['emacs', 'vi']) }
+  let(:candidate_version) { [ "1.0", "6.2" ] }
+  let(:provider) do
+    provider = Chef::Provider::Package.new(new_resource, run_context)
+    provider.current_resource = current_resource
+    provider.candidate_version = candidate_version
+    provider
   end
 
   describe "when installing multiple packages" do
     before(:each) do
-      @provider.current_resource = @current_resource
-      allow(@provider).to receive(:install_package).and_return(true)
+      provider.current_resource = current_resource
+      allow(provider).to receive(:install_package).and_return(true)
     end
 
     it "installs the candidate versions when none are installed" do
-      expect(@provider).to receive(:install_package).with(
+      expect(provider).to receive(:install_package).with(
         ["emacs", "vi"],
         ["1.0", "6.2"]
       ).and_return(true)
-      @provider.run_action(:install)
-      expect(@new_resource).to be_updated
+      provider.run_action(:install)
+      expect(new_resource).to be_updated
     end
 
     it "installs the candidate versions when some are installed" do
-      expect(@provider).to receive(:install_package).with(
+      expect(provider).to receive(:install_package).with(
         [ 'vi' ],
         [ '6.2' ]
       ).and_return(true)
-      @current_resource.version(['1.0', nil])
-      @provider.run_action(:install)
-      expect(@new_resource).to be_updated
+      current_resource.version(['1.0', nil])
+      provider.run_action(:install)
+      expect(new_resource).to be_updated
     end
 
     it "installs the specified version when some are out of date" do
-      @current_resource.version(['1.0', '6.2'])
-      @new_resource.version(['1.0', '6.1'])
-      @provider.run_action(:install)
-      expect(@new_resource).to be_updated
+      current_resource.version(['1.0', '6.2'])
+      new_resource.version(['1.0', '6.1'])
+      provider.run_action(:install)
+      expect(new_resource).to be_updated
     end
 
     it "does not install any version if all are installed at the right version" do
-      @current_resource.version(['1.0', '6.2'])
-      @new_resource.version(['1.0', '6.2'])
-      @provider.run_action(:install)
-      expect(@new_resource).not_to be_updated_by_last_action
+      current_resource.version(['1.0', '6.2'])
+      new_resource.version(['1.0', '6.2'])
+      provider.run_action(:install)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "does not install any version if all are installed, and no version was specified" do
-      @current_resource.version(['1.0', '6.2'])
-      @provider.run_action(:install)
-      expect(@new_resource).not_to be_updated_by_last_action
+      current_resource.version(['1.0', '6.2'])
+      provider.run_action(:install)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "raises an exception if both are not installed and no caondidates are available" do
-      @current_resource.version([nil, nil])
-      @provider.candidate_version = [nil, nil]
-      expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+      current_resource.version([nil, nil])
+      provider.candidate_version = [nil, nil]
+      expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
     end
 
     it "raises an exception if one is not installed and no candidates are available" do
-      @current_resource.version(['1.0', nil])
-      @provider.candidate_version = ['1.0', nil]
-      expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+      current_resource.version(['1.0', nil])
+      provider.candidate_version = ['1.0', nil]
+      expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
     end
 
     it "does not raise an exception if the packages are installed or have a candidate" do
-      @current_resource.version(['1.0', nil])
-      @provider.candidate_version = [nil, '6.2']
-      expect { @provider.run_action(:install) }.not_to raise_error
+      current_resource.version(['1.0', nil])
+      provider.candidate_version = [nil, '6.2']
+      expect { provider.run_action(:install) }.not_to raise_error
     end
 
     it "raises an exception if an explicit version is asked for, an old version is installed, but no candidate" do
-      @new_resource.version ['1.0', '6.2']
-      @current_resource.version(['1.0', '6.1'])
-      @provider.candidate_version = ['1.0', nil]
-      expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+      new_resource.version ['1.0', '6.2']
+      current_resource.version(['1.0', '6.1'])
+      provider.candidate_version = ['1.0', nil]
+      expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
     end
 
     it "does not raise an exception if an explicit version is asked for, and is installed, but no candidate" do
-      @new_resource.version ['1.0', '6.2']
-      @current_resource.version(['1.0', '6.2'])
-      @provider.candidate_version = ['1.0', nil]
-      expect { @provider.run_action(:install) }.not_to raise_error
+      new_resource.version ['1.0', '6.2']
+      current_resource.version(['1.0', '6.2'])
+      provider.candidate_version = ['1.0', nil]
+      expect { provider.run_action(:install) }.not_to raise_error
     end
 
     it "raise an exception if an explicit version is asked for, and is not installed, and no candidate" do
-      @new_resource.version ['1.0', '6.2']
-      @current_resource.version(['1.0', nil])
-      @provider.candidate_version = ['1.0', nil]
-      expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+      new_resource.version ['1.0', '6.2']
+      current_resource.version(['1.0', nil])
+      provider.candidate_version = ['1.0', nil]
+      expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
     end
 
     it "does not raise an exception if an explicit version is asked for, and is not installed, and there is a candidate" do
-      @new_resource.version ['1.0', '6.2']
-      @current_resource.version(['1.0', nil])
-      @provider.candidate_version = ['1.0', '6.2']
-      expect { @provider.run_action(:install) }.not_to raise_error
+      new_resource.version ['1.0', '6.2']
+      current_resource.version(['1.0', nil])
+      provider.candidate_version = ['1.0', '6.2']
+      expect { provider.run_action(:install) }.not_to raise_error
     end
   end
 
   describe "when upgrading multiple packages" do
     before(:each) do
-      @provider.current_resource = @current_resource
-      allow(@provider).to receive(:upgrade_package).and_return(true)
+      provider.current_resource = current_resource
+      allow(provider).to receive(:upgrade_package).and_return(true)
     end
 
     it "should upgrade the package if the current versions are not the candidate version" do
-      @current_resource.version ['0.9', '6.1']
-      expect(@provider).to receive(:upgrade_package).with(
-        @new_resource.package_name,
-        @provider.candidate_version
+      current_resource.version ['0.9', '6.1']
+      expect(provider).to receive(:upgrade_package).with(
+        new_resource.package_name,
+        provider.candidate_version
       ).and_return(true)
-      @provider.run_action(:upgrade)
-      expect(@new_resource).to be_updated_by_last_action
+      provider.run_action(:upgrade)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should upgrade the package if some of current versions are not the candidate versions" do
-      @current_resource.version ['1.0', '6.1']
-      expect(@provider).to receive(:upgrade_package).with(
+      current_resource.version ['1.0', '6.1']
+      expect(provider).to receive(:upgrade_package).with(
         ["vi"],
         ["6.2"]
       ).and_return(true)
-      @provider.run_action(:upgrade)
-      expect(@new_resource).to be_updated_by_last_action
+      provider.run_action(:upgrade)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should not install the package if the current versions are the candidate version" do
-      @current_resource.version ['1.0', '6.2']
-      expect(@provider).not_to receive(:upgrade_package)
-      @provider.run_action(:upgrade)
-      expect(@new_resource).not_to be_updated_by_last_action
+      current_resource.version ['1.0', '6.2']
+      expect(provider).not_to receive(:upgrade_package)
+      provider.run_action(:upgrade)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should raise an exception if both are not installed and no caondidates are available" do
-      @current_resource.version([nil, nil])
-      @provider.candidate_version = [nil, nil]
-      expect { @provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
+      current_resource.version([nil, nil])
+      provider.candidate_version = [nil, nil]
+      expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
     end
 
     it "should raise an exception if one is not installed and no candidates are available" do
-      @current_resource.version(['1.0', nil])
-      @provider.candidate_version = ['1.0', nil]
-      expect { @provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
+      current_resource.version(['1.0', nil])
+      provider.candidate_version = ['1.0', nil]
+      expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package)
     end
 
     it "should not raise an exception if the packages are installed or have a candidate" do
-      @current_resource.version(['1.0', nil])
-      @provider.candidate_version = [nil, '6.2']
-      expect { @provider.run_action(:upgrade) }.not_to raise_error
+      current_resource.version(['1.0', nil])
+      provider.candidate_version = [nil, '6.2']
+      expect { provider.run_action(:upgrade) }.not_to raise_error
     end
 
     it "should not raise an exception if the packages are installed or have a candidate" do
-      @current_resource.version(['1.0', nil])
-      @provider.candidate_version = [nil, '6.2']
-      expect { @provider.run_action(:upgrade) }.not_to raise_error
+      current_resource.version(['1.0', nil])
+      provider.candidate_version = [nil, '6.2']
+      expect { provider.run_action(:upgrade) }.not_to raise_error
     end
   end
 
   describe "When removing multiple packages " do
     before(:each) do
-      allow(@provider).to receive(:remove_package).and_return(true)
-      @current_resource.version ['1.0', '6.2']
+      allow(provider).to receive(:remove_package).and_return(true)
+      current_resource.version ['1.0', '6.2']
     end
 
     it "should remove the packages if all are installed" do
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:remove_package).with(['emacs', 'vi'], nil)
-      @provider.run_action(:remove)
-      expect(@new_resource).to be_updated
-      expect(@new_resource).to be_updated_by_last_action
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:remove_package).with(['emacs', 'vi'], nil)
+      provider.run_action(:remove)
+      expect(new_resource).to be_updated
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should remove the packages if some are installed" do
-      @current_resource.version ['1.0', nil]
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:remove_package).with(['emacs', 'vi'], nil)
-      @provider.run_action(:remove)
-      expect(@new_resource).to be_updated
-      expect(@new_resource).to be_updated_by_last_action
+      current_resource.version ['1.0', nil]
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:remove_package).with(['emacs', 'vi'], nil)
+      provider.run_action(:remove)
+      expect(new_resource).to be_updated
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should remove the packages at a specific version if they are installed at that version" do
-      @new_resource.version ['1.0', '6.2']
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:remove_package).with(['emacs', 'vi'], ['1.0', '6.2'])
-      @provider.run_action(:remove)
-      expect(@new_resource).to be_updated_by_last_action
+      new_resource.version ['1.0', '6.2']
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:remove_package).with(['emacs', 'vi'], ['1.0', '6.2'])
+      provider.run_action(:remove)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should remove the packages at a specific version any are is installed at that version" do
-      @new_resource.version ['0.5', '6.2']
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:remove_package).with(['emacs', 'vi'], ['0.5', '6.2'])
-      @provider.run_action(:remove)
-      expect(@new_resource).to be_updated_by_last_action
+      new_resource.version ['0.5', '6.2']
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:remove_package).with(['emacs', 'vi'], ['0.5', '6.2'])
+      provider.run_action(:remove)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should not remove the packages at a specific version if they are not installed at that version" do
-      @new_resource.version ['0.5', '6.0']
-      expect(@provider).not_to be_removing_package
-      expect(@provider).not_to receive(:remove_package)
-      @provider.run_action(:remove)
-      expect(@new_resource).not_to be_updated_by_last_action
+      new_resource.version ['0.5', '6.0']
+      expect(provider).not_to be_removing_package
+      expect(provider).not_to receive(:remove_package)
+      provider.run_action(:remove)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should not remove the packages if they are not installed" do
-      expect(@provider).not_to receive(:remove_package)
-      allow(@current_resource).to receive(:version).and_return(nil)
-      @provider.run_action(:remove)
-      expect(@new_resource).not_to be_updated_by_last_action
+      expect(provider).not_to receive(:remove_package)
+      allow(current_resource).to receive(:version).and_return(nil)
+      provider.run_action(:remove)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
   end
 
   describe "When purging multiple packages " do
     before(:each) do
-      allow(@provider).to receive(:purge_package).and_return(true)
-      @current_resource.version ['1.0', '6.2']
+      allow(provider).to receive(:purge_package).and_return(true)
+      current_resource.version ['1.0', '6.2']
     end
 
     it "should purge the packages if all are installed" do
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:purge_package).with(['emacs', 'vi'], nil)
-      @provider.run_action(:purge)
-      expect(@new_resource).to be_updated
-      expect(@new_resource).to be_updated_by_last_action
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:purge_package).with(['emacs', 'vi'], nil)
+      provider.run_action(:purge)
+      expect(new_resource).to be_updated
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should purge the packages if some are installed" do
-      @current_resource.version ['1.0', nil]
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:purge_package).with(['emacs', 'vi'], nil)
-      @provider.run_action(:purge)
-      expect(@new_resource).to be_updated
-      expect(@new_resource).to be_updated_by_last_action
+      current_resource.version ['1.0', nil]
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:purge_package).with(['emacs', 'vi'], nil)
+      provider.run_action(:purge)
+      expect(new_resource).to be_updated
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should purge the packages at a specific version if they are installed at that version" do
-      @new_resource.version ['1.0', '6.2']
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:purge_package).with(['emacs', 'vi'], ['1.0', '6.2'])
-      @provider.run_action(:purge)
-      expect(@new_resource).to be_updated_by_last_action
+      new_resource.version ['1.0', '6.2']
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:purge_package).with(['emacs', 'vi'], ['1.0', '6.2'])
+      provider.run_action(:purge)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should purge the packages at a specific version any are is installed at that version" do
-      @new_resource.version ['0.5', '6.2']
-      expect(@provider).to be_removing_package
-      expect(@provider).to receive(:purge_package).with(['emacs', 'vi'], ['0.5', '6.2'])
-      @provider.run_action(:purge)
-      expect(@new_resource).to be_updated_by_last_action
+      new_resource.version ['0.5', '6.2']
+      expect(provider).to be_removing_package
+      expect(provider).to receive(:purge_package).with(['emacs', 'vi'], ['0.5', '6.2'])
+      provider.run_action(:purge)
+      expect(new_resource).to be_updated_by_last_action
     end
 
     it "should not purge the packages at a specific version if they are not installed at that version" do
-      @new_resource.version ['0.5', '6.0']
-      expect(@provider).not_to be_removing_package
-      expect(@provider).not_to receive(:purge_package)
-      @provider.run_action(:purge)
-      expect(@new_resource).not_to be_updated_by_last_action
+      new_resource.version ['0.5', '6.0']
+      expect(provider).not_to be_removing_package
+      expect(provider).not_to receive(:purge_package)
+      provider.run_action(:purge)
+      expect(new_resource).not_to be_updated_by_last_action
     end
 
     it "should not purge the packages if they are not installed" do
-      expect(@provider).not_to receive(:purge_package)
-      allow(@current_resource).to receive(:version).and_return(nil)
-      @provider.run_action(:purge)
-      expect(@new_resource).not_to be_updated_by_last_action
+      expect(provider).not_to receive(:purge_package)
+      allow(current_resource).to receive(:version).and_return(nil)
+      provider.run_action(:purge)
+      expect(new_resource).not_to be_updated_by_last_action
+    end
+  end
+
+  describe "shell_out helpers" do
+    [ :shell_out_with_timeout, :shell_out_with_timeout! ].each do |method|
+      stubbed_method = method == :shell_out_with_timeout! ? :shell_out! : :shell_out
+      [ %w{command arg1 arg2}, "command arg1 arg2" ].each do |command|
+        it "#{method} defaults to 900 seconds" do
+          expect(provider).to receive(stubbed_method).with(*command, timeout: 900)
+          provider.send(method, *command)
+        end
+        it "#{method} overrides the default timeout with its options" do
+          expect(provider).to receive(stubbed_method).with(*command, timeout: 1)
+          provider.send(method, *command, timeout: 1)
+        end
+        it "#{method} overrides both timeouts with the new_resource.timeout" do
+          new_resource.timeout(99)
+          expect(provider).to receive(stubbed_method).with(*command, timeout: 99)
+          provider.send(method, *command, timeout: 1)
+        end
+        it "#{method} defaults to 900 seconds and preserves options" do
+          expect(provider).to receive(stubbed_method).with(*command, env: nil, timeout: 900)
+          provider.send(method, *command, env: nil)
+        end
+        it "#{method} overrides the default timeout with its options and preserves options" do
+          expect(provider).to receive(stubbed_method).with(*command, timeout: 1, env: nil)
+          provider.send(method, *command, timeout: 1, env: nil)
+        end
+        it "#{method} overrides both timeouts with the new_resource.timeout and preseves options" do
+          new_resource.timeout(99)
+          expect(provider).to receive(stubbed_method).with(*command, timeout: 99, env: nil)
+          provider.send(method, *command, timeout: 1, env: nil)
+        end
+      end
     end
   end
 end
diff --git a/spec/unit/provider/package_spec.rbe b/spec/unit/provider/package_spec.rbe
deleted file mode 100644
index e69de29..0000000
diff --git a/spec/unit/provider/powershell_script_spec.rb b/spec/unit/provider/powershell_script_spec.rb
new file mode 100644
index 0000000..1219737
--- /dev/null
+++ b/spec/unit/provider/powershell_script_spec.rb
@@ -0,0 +1,106 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, 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 'spec_helper'
+describe Chef::Provider::PowershellScript, "action_run" do
+
+  let(:powershell_version) { nil }
+  let(:node) {
+    node = Chef::Node.new
+    node.default["kernel"] = Hash.new
+    node.default["kernel"][:machine] = :x86_64.to_s
+    if ! powershell_version.nil?
+      node.default[:languages] = { :powershell => { :version => powershell_version } }
+    end
+    node
+  }
+
+  let(:provider) {
+    empty_events = Chef::EventDispatch::Dispatcher.new
+    run_context = Chef::RunContext.new(node, {}, empty_events)
+    new_resource = Chef::Resource::PowershellScript.new('run some powershell code', run_context)
+    Chef::Provider::PowershellScript.new(new_resource, run_context)
+  }
+
+  context 'when setting interpreter flags' do
+    context 'on nano' do
+      before(:each) do
+        allow(Chef::Platform).to receive(:windows_nano_server?).and_return(true)
+        allow(provider).to receive(:is_forced_32bit).and_return(false)
+        os_info_double = double("os_info")
+        allow(provider.run_context.node.kernel).to receive(:os_info).and_return(os_info_double)
+        allow(os_info_double).to receive(:system_directory).and_return("C:\\Windows\\system32")
+      end
+
+      it "sets the -Command flag as the last flag" do
+        flags = provider.command.split(' ').keep_if { |flag| flag =~ /^-/ }
+        expect(flags.pop).to eq("-Command")
+      end
+    end
+
+    context 'not on nano' do
+      before(:each) do
+        allow(Chef::Platform).to receive(:windows_nano_server?).and_return(false)
+        allow(provider).to receive(:is_forced_32bit).and_return(false)
+        os_info_double = double("os_info")
+        allow(provider.run_context.node.kernel).to receive(:os_info).and_return(os_info_double)
+        allow(os_info_double).to receive(:system_directory).and_return("C:\\Windows\\system32")
+      end
+
+      it "sets the -File flag as the last flag" do
+        flags = provider.command.split(' ').keep_if { |flag| flag =~ /^-/ }
+        expect(flags.pop).to eq("-File")
+      end
+
+      let(:execution_policy_flag) do
+        execution_policy_index = 0
+        provider_flags = provider.flags.split(' ')
+        execution_policy_specified = false
+
+        provider_flags.find do | value |
+          execution_policy_index += 1
+          execution_policy_specified = value.downcase == '-ExecutionPolicy'.downcase
+        end
+
+        execution_policy = execution_policy_specified ? provider_flags[execution_policy_index] : nil
+      end
+
+      context 'when running with an unspecified PowerShell version' do
+        let(:powershell_version) { nil }
+        it "sets the -ExecutionPolicy flag to 'Unrestricted' by default" do
+          expect(execution_policy_flag.downcase).to eq('unrestricted'.downcase)
+        end
+      end
+
+      { '2.0' => 'Unrestricted',
+        '2.5' => 'Unrestricted',
+        '3.0' => 'Bypass',
+        '3.6' => 'Bypass',
+        '4.0' => 'Bypass',
+        '5.0' => 'Bypass' }.each do | version_policy |
+        let(:powershell_version) { version_policy[0].to_f }
+        context "when running PowerShell version #{version_policy[0]}" do
+          let(:powershell_version) { version_policy[0].to_f }
+          it "sets the -ExecutionPolicy flag to '#{version_policy[1]}'" do
+            expect(execution_policy_flag.downcase).to eq(version_policy[1].downcase)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/powershell_spec.rb b/spec/unit/provider/powershell_spec.rb
deleted file mode 100644
index 60dbcf8..0000000
--- a/spec/unit/provider/powershell_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# Author:: Adam Edwards (<adamed at opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, 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 'spec_helper'
-describe Chef::Provider::PowershellScript, "action_run" do
-
-  before(:each) do
-    @node = Chef::Node.new
-
-    @node.default["kernel"] = Hash.new
-    @node.default["kernel"][:machine] = :x86_64.to_s
-
-    @run_context = Chef::RunContext.new(@node, {}, @events)
-    @new_resource = Chef::Resource::PowershellScript.new('run some powershell code', @run_context)
-
-    @provider = Chef::Provider::PowershellScript.new(@new_resource, @run_context)
-  end
-
-  it "should set the -File flag as the last flag" do
-    expect(@provider.flags.split(' ').pop).to eq("-File")
-  end
-
-end
diff --git a/spec/unit/provider/registry_key_spec.rb b/spec/unit/provider/registry_key_spec.rb
index 79811fd..47543ff 100644
--- a/spec/unit/provider/registry_key_spec.rb
+++ b/spec/unit/provider/registry_key_spec.rb
@@ -77,6 +77,18 @@ shared_examples_for "a registry key" do
   end
 
   describe "action_create" do
+    context "when a case insensitive match for the key exists" do
+      before(:each) do
+        expect(@double_registry).to receive(:key_exists?).twice.with(keyname.downcase).and_return(true)
+      end
+      it "should do nothing if the if a case insensitive key and the value both exist" do
+        @provider.new_resource.key(keyname.downcase)
+        expect(@double_registry).to receive(:get_values).with(keyname.downcase).and_return( testval1 )
+        expect(@double_registry).not_to receive(:set_value)
+        @provider.load_current_resource
+        @provider.action_create
+      end
+    end
     context "when the key exists" do
       before(:each) do
         expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(true)
diff --git a/spec/unit/provider/remote_directory_spec.rb b/spec/unit/provider/remote_directory_spec.rb
index 4434714..6426daf 100644
--- a/spec/unit/provider/remote_directory_spec.rb
+++ b/spec/unit/provider/remote_directory_spec.rb
@@ -79,7 +79,7 @@ describe Chef::Provider::RemoteDirectory do
     end
 
     it "configures access control on intermediate directorys" do
-      directory_resource = @provider.send(:resource_for_directory, File.join(Dir.tmpdir, "intermediate_dir"))
+      directory_resource = @provider.send(:directory_resource, File.join(Dir.tmpdir, "intermediate_dir"))
       expect(directory_resource.path).to  eq(File.join(Dir.tmpdir, "intermediate_dir"))
       expect(directory_resource.mode).to  eq("0750")
       expect(directory_resource.group).to eq("wheel")
@@ -194,8 +194,8 @@ describe Chef::Provider::RemoteDirectory do
 
             expect(::File.exist?(symlinked_dir_path)).to be_falsey
             expect(::File.exist?(tmp_dir)).to be_truthy
-          rescue Chef::Exceptions::Win32APIError => e
-            pending "This must be run as an Administrator to create symlinks"
+          rescue Chef::Exceptions::Win32APIError
+            skip "This must be run as an Administrator to create symlinks"
           end
         end
       end
@@ -219,4 +219,3 @@ describe Chef::Provider::RemoteDirectory do
 
   end
 end
-
diff --git a/spec/unit/provider/remote_file/cache_control_data_spec.rb b/spec/unit/provider/remote_file/cache_control_data_spec.rb
index 11f2af3..c154c42 100644
--- a/spec/unit/provider/remote_file/cache_control_data_spec.rb
+++ b/spec/unit/provider/remote_file/cache_control_data_spec.rb
@@ -20,12 +20,12 @@ require 'spec_helper'
 require 'uri'
 
 CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH = 64
-CACHE_FILE_MD5_HEX_LENGTH = 32
+CACHE_FILE_CHECKSUM_HEX_LENGTH = 32
 CACHE_FILE_JSON_FILE_EXTENSION_LENGTH = 5
 CACHE_FILE_PATH_LIMIT =
   CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH +
   1 +
-  CACHE_FILE_MD5_HEX_LENGTH +
+  CACHE_FILE_CHECKSUM_HEX_LENGTH +
   CACHE_FILE_JSON_FILE_EXTENSION_LENGTH # {friendly}-{md5hex}.json == 102
 
 describe Chef::Provider::RemoteFile::CacheControlData do
@@ -36,7 +36,8 @@ describe Chef::Provider::RemoteFile::CacheControlData do
     Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, current_file_checksum)
   end
 
-  let(:cache_path) { "remote_file/http___www_google_com_robots_txt-9839677abeeadf0691026e0cabca2339.json" }
+  let(:cache_path) { "remote_file/http___www_google_com_robots_txt-6dc1b24315d0cff764d30344199c6f7b.json" }
+  let(:old_cache_path) { "remote_file/http___www_google_com_robots_txt-9839677abeeadf0691026e0cabca2339.json" }
 
   # the checksum of the file we have on disk already
   let(:current_file_checksum) { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }
@@ -44,7 +45,8 @@ describe Chef::Provider::RemoteFile::CacheControlData do
   context "when loading data for an unknown URI" do
 
     before do
-      expect(Chef::FileCache).to receive(:load).with(cache_path).and_raise(Chef::Exceptions::FileNotFound, "nope")
+      expect(Chef::FileCache).to receive(:has_key?).with(cache_path).and_return(false)
+      expect(Chef::FileCache).to receive(:has_key?).with(old_cache_path).and_return(false)
     end
 
     context "and there is no current copy of the file" do
@@ -64,7 +66,8 @@ describe Chef::Provider::RemoteFile::CacheControlData do
     context "and the URI contains a password" do
 
       let(:uri) { URI.parse("http://bob:password@example.org/") }
-      let(:cache_path) { "remote_file/http___bob_XXXX_example_org_-f121caacb74c05a35bcefdf578ed5fc9.json" }
+      let(:cache_path) { "remote_file/http___bob_XXXX_example_org_-44be109aa176a165ef599c12d97af792.json" }
+      let(:old_cache_path) { "remote_file/http___bob_XXXX_example_org_-f121caacb74c05a35bcefdf578ed5fc9.json" }
 
       it "loads the cache data from a path based on a sanitized URI" do
         Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, current_file_checksum)
@@ -88,51 +91,73 @@ describe Chef::Provider::RemoteFile::CacheControlData do
       Chef::JSONCompat.to_json(cache)
     end
 
-    before do
-      expect(Chef::FileCache).to receive(:load).with(cache_path).and_return(cache_json_data)
-    end
-
-    context "and there is no on-disk copy of the file" do
-      let(:current_file_checksum) { nil }
-
-      it "returns empty cache control data" do
-        expect(cache_control_data.etag).to be_nil
-        expect(cache_control_data.mtime).to be_nil
+    context "when the cache control data uses sha256 for its name" do
+      before do
+        expect(Chef::FileCache).to receive(:has_key?).with(cache_path).and_return(true)
+        expect(Chef::FileCache).to receive(:load).with(cache_path).and_return(cache_json_data)
       end
-    end
 
-    context "and the cached checksum does not match the on-disk copy" do
-      let(:current_file_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" }
+      context "and there is no on-disk copy of the file" do
+        let(:current_file_checksum) { nil }
 
-      it "returns empty cache control data" do
-        expect(cache_control_data.etag).to be_nil
-        expect(cache_control_data.mtime).to be_nil
+        it "returns empty cache control data" do
+          expect(cache_control_data.etag).to be_nil
+          expect(cache_control_data.mtime).to be_nil
+        end
       end
-    end
 
-    context "and the cached checksum matches the on-disk copy" do
+      context "and the cached checksum does not match the on-disk copy" do
+        let(:current_file_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" }
 
-      it "populates the cache control data" do
-        expect(cache_control_data.etag).to eq(etag)
-        expect(cache_control_data.mtime).to eq(mtime)
+        it "returns empty cache control data" do
+          expect(cache_control_data.etag).to be_nil
+          expect(cache_control_data.mtime).to be_nil
+        end
       end
-    end
-
-    context "and the cached checksum data is corrupted" do
-      let(:cache_json_data) { '{"foo",,"bar" []}' }
 
-      it "returns empty cache control data" do
-        expect(cache_control_data.etag).to be_nil
-        expect(cache_control_data.mtime).to be_nil
+      context "and the cached checksum matches the on-disk copy" do
+        context "when the filename uses sha256" do
+          before do
+            expect(Chef::FileCache).not_to receive(:has_key?).with(old_cache_path)
+          end
+          it "populates the cache control data" do
+            expect(cache_control_data.etag).to eq(etag)
+            expect(cache_control_data.mtime).to eq(mtime)
+          end
+        end
       end
 
-      context "and it still is valid JSON" do
-        let(:cache_json_data) { '' }
+      context "and the cached checksum data is corrupted" do
+        let(:cache_json_data) { '{"foo",,"bar" []}' }
 
         it "returns empty cache control data" do
           expect(cache_control_data.etag).to be_nil
           expect(cache_control_data.mtime).to be_nil
         end
+
+        context "and it still is valid JSON" do
+          let(:cache_json_data) { '' }
+
+          it "returns empty cache control data" do
+            expect(cache_control_data.etag).to be_nil
+            expect(cache_control_data.mtime).to be_nil
+          end
+        end
+      end
+    end
+
+    context "when the filename uses md5" do
+      before do
+        expect(Chef::FileCache).to receive(:has_key?).with(cache_path).and_return(false)
+        expect(Chef::FileCache).to receive(:has_key?).with(old_cache_path).and_return(true)
+        expect(Chef::FileCache).to receive(:load).with(old_cache_path).and_return(cache_json_data)
+      end
+
+      it "populates the cache control data and creates the cache control data file with the correct path" do
+        expect(Chef::FileCache).to receive(:store).with(cache_path, cache_json_data)
+        expect(Chef::FileCache).to receive(:delete).with(old_cache_path)
+        expect(cache_control_data.etag).to eq(etag)
+        expect(cache_control_data.mtime).to eq(mtime)
       end
     end
   end
@@ -174,7 +199,8 @@ describe Chef::Provider::RemoteFile::CacheControlData do
     context "and the URI contains a password" do
 
       let(:uri) { URI.parse("http://bob:password@example.org/") }
-      let(:cache_path) { "remote_file/http___bob_XXXX_example_org_-f121caacb74c05a35bcefdf578ed5fc9.json" }
+      let(:cache_path) { "remote_file/http___bob_XXXX_example_org_-44be109aa176a165ef599c12d97af792.json" }
+      let(:old_cache_path) { "remote_file/http___bob_XXXX_example_org_-f121caacb74c05a35bcefdf578ed5fc9.json" }
 
       it "writes the data to the cache with a sanitized path name" do
         json_data = cache_control_data.json_data
diff --git a/spec/unit/provider/remote_file/fetcher_spec.rb b/spec/unit/provider/remote_file/fetcher_spec.rb
index c049848..8bd3b7c 100644
--- a/spec/unit/provider/remote_file/fetcher_spec.rb
+++ b/spec/unit/provider/remote_file/fetcher_spec.rb
@@ -24,6 +24,26 @@ describe Chef::Provider::RemoteFile::Fetcher do
   let(:new_resource) { double("new resource") }
   let(:fetcher_instance) { double("fetcher") }
 
+  describe "when passed a network share" do
+    before do
+      expect(Chef::Provider::RemoteFile::NetworkFile).to receive(:new).and_return(fetcher_instance)
+    end
+
+    context "when host is a name" do
+      let(:source) { "\\\\foohost\\fooshare\\Foo.tar.gz" }
+      it "returns a network file fetcher" do
+        expect(described_class.for_resource(source, new_resource, current_resource)).to eq(fetcher_instance)
+      end
+    end
+
+    context "when host is an ip" do
+      let(:source) { "\\\\127.0.0.1\\fooshare\\Foo.tar.gz" }
+      it "returns a network file fetcher" do
+        expect(described_class.for_resource(source, new_resource, current_resource)).to eq(fetcher_instance)
+      end
+    end
+  end
+
   describe "when passed an http url" do
     let(:uri) { double("uri", :scheme => "http" ) }
     before do
@@ -72,4 +92,3 @@ describe Chef::Provider::RemoteFile::Fetcher do
   end
 
 end
-
diff --git a/spec/unit/provider/remote_file/local_file_spec.rb b/spec/unit/provider/remote_file/local_file_spec.rb
index b33d82f..575996a 100644
--- a/spec/unit/provider/remote_file/local_file_spec.rb
+++ b/spec/unit/provider/remote_file/local_file_spec.rb
@@ -25,26 +25,45 @@ describe Chef::Provider::RemoteFile::LocalFile do
   let(:new_resource) { Chef::Resource::RemoteFile.new("local file backend test (new_resource)") }
   let(:current_resource) { Chef::Resource::RemoteFile.new("local file backend test (current_resource)") }
   subject(:fetcher) { Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource) }
-  
-  context "when parsing source path" do
+
+  context "when parsing source path on windows" do
+
+    before do
+      allow(Chef::Platform).to receive(:windows?).and_return(true)
+    end
+
     describe "when given local unix path" do
       let(:uri) { URI.parse("file:///nyan_cat.png") }
       it "returns a correct unix path" do
-        expect(fetcher.fix_windows_path(uri.path)).to eq("/nyan_cat.png")
+        expect(fetcher.source_path).to eq("/nyan_cat.png")
       end
     end
 
     describe "when given local windows path" do
       let(:uri) { URI.parse("file:///z:/windows/path/file.txt") }
       it "returns a valid windows local path" do
-        expect(fetcher.fix_windows_path(uri.path)).to eq("z:/windows/path/file.txt")
+        expect(fetcher.source_path).to eq("z:/windows/path/file.txt")
+      end
+    end
+
+    describe "when given local windows path with spaces" do
+      let(:uri) { URI.parse(URI.escape("file:///z:/windows/path/foo & bar.txt")) }
+      it "returns a valid windows local path" do
+        expect(fetcher.source_path).to eq("z:/windows/path/foo & bar.txt")
       end
     end
 
     describe "when given unc windows path" do
       let(:uri) { URI.parse("file:////server/share/windows/path/file.txt") }
       it "returns a valid windows unc path" do
-        expect(fetcher.fix_windows_path(uri.path)).to eq("//server/share/windows/path/file.txt")
+        expect(fetcher.source_path).to eq("//server/share/windows/path/file.txt")
+      end
+    end
+
+    describe "when given unc windows path with spaces" do
+      let(:uri) { URI.parse(URI.escape("file:////server/share/windows/path/foo & bar.txt")) }
+      it "returns a valid windows unc path" do
+        expect(fetcher.source_path).to eq("//server/share/windows/path/foo & bar.txt")
       end
     end
   end
@@ -73,7 +92,7 @@ describe Chef::Provider::RemoteFile::LocalFile do
     it "stages the local file to a temporary file" do
       expect(Chef::FileContentManagement::Tempfile).to receive(:new).with(new_resource).and_return(chef_tempfile)
       expect(::FileUtils).to receive(:cp).with(uri.path, tempfile.path)
-      expect(tempfile).to receive(:close)            
+      expect(tempfile).to receive(:close)
 
       result = fetcher.fetch
       expect(result).to eq(tempfile)
diff --git a/spec/unit/provider/remote_file/network_file_spec.rb b/spec/unit/provider/remote_file/network_file_spec.rb
new file mode 100644
index 0000000..3666a47
--- /dev/null
+++ b/spec/unit/provider/remote_file/network_file_spec.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Jay Mundrawala (<jdm at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software
+# 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 'spec_helper'
+
+describe Chef::Provider::RemoteFile::NetworkFile do
+
+  let(:source) { "\\\\foohost\\fooshare\\Foo.tar.gz" }
+
+  let(:new_resource) { Chef::Resource::RemoteFile.new("network file (new_resource)") }
+  let(:current_resource) { Chef::Resource::RemoteFile.new("network file (current_resource)") }
+  subject(:fetcher) { Chef::Provider::RemoteFile::NetworkFile.new(source, new_resource, current_resource) }
+
+  describe "when fetching the object" do
+
+    let(:tempfile) { double("Tempfile", :path => "/tmp/foo/bar/Foo.tar.gz", :close => nil) }
+    let(:chef_tempfile) { double("Chef::FileContentManagement::Tempfile", :tempfile => tempfile) }
+
+    it "stages the local file to a temporary file" do
+      expect(Chef::FileContentManagement::Tempfile).to receive(:new).with(new_resource).and_return(chef_tempfile)
+      expect(::FileUtils).to receive(:cp).with(source, tempfile.path)
+      expect(tempfile).to receive(:close)
+
+      result = fetcher.fetch
+      expect(result).to eq(tempfile)
+    end
+
+  end
+
+end
diff --git a/spec/unit/provider/script_spec.rb b/spec/unit/provider/script_spec.rb
index d175998..7cc5abb 100644
--- a/spec/unit/provider/script_spec.rb
+++ b/spec/unit/provider/script_spec.rb
@@ -88,11 +88,11 @@ describe Chef::Provider::Script, "action_run" do
 
     describe "when running the script" do
       let (:default_opts) {
-        {timeout: 3600, returns: 0, log_level: :info, log_tag: "script[run some perl code]", live_stream: STDOUT}
+        { timeout: 3600, returns: 0, log_level: :info, log_tag: "script[run some perl code]" }
       }
 
       before do
-        allow(STDOUT).to receive(:tty?).and_return(true)
+        allow(STDOUT).to receive(:tty?).and_return(false)
       end
 
       it 'should set the command to "interpreter"  "tempfile"' do
diff --git a/spec/unit/provider/service/aix_service_spec.rb b/spec/unit/provider/service/aix_service_spec.rb
index 7966611..5cca7d6 100644
--- a/spec/unit/provider/service/aix_service_spec.rb
+++ b/spec/unit/provider/service/aix_service_spec.rb
@@ -51,22 +51,35 @@ describe Chef::Provider::Service::Aix do
       end
 
       it "current resource is running" do
-        expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
-        expect(@provider).to receive(:is_resource_group?).with(["chef chef 12345 active"])
+        expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status)
+        expect(@provider).to receive(:is_resource_group?).and_return false
 
         @provider.load_current_resource
         expect(@current_resource.running).to be_truthy
       end
     end
 
-    context "when the service is inoprative" do
+    context "when the service is inoperative" do
       before do
         @status = double("Status", :exitstatus => 0, :stdout => "chef chef inoperative\n")
       end
 
       it "current resource is not running" do
-        expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
-        expect(@provider).to receive(:is_resource_group?).with(["chef chef inoperative"])
+        expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status)
+        expect(@provider).to receive(:is_resource_group?).and_return false
+
+        @provider.load_current_resource
+        expect(@current_resource.running).to be_falsey
+      end
+    end
+
+    context "when there is no such service" do
+      before do
+        @status = double("Status", :exitstatus => 1, :stdout => "0513-085 The chef Subsystem is not on file.\n")
+      end
+      it "current resource is not running" do
+        expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status)
+        expect(@provider).to receive(:is_resource_group?).and_return false
 
         @provider.load_current_resource
         expect(@current_resource.running).to be_falsey
@@ -75,13 +88,13 @@ describe Chef::Provider::Service::Aix do
   end
 
   describe "is resource group" do
-    context "when there are mutiple subsystems associated with group" do
+    context "when there are multiple subsystems associated with group" do
       before do
         @status = double("Status", :exitstatus => 0, :stdout => "chef1 chef 12345 active\nchef2 chef 12334 active\nchef3 chef inoperative")
       end
 
       it "service is a group" do
-        expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
+        expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@status)
         @provider.load_current_resource
         expect(@provider.instance_eval("@is_resource_group")).to be_truthy
       end
@@ -93,19 +106,21 @@ describe Chef::Provider::Service::Aix do
       end
 
       it "service is a group" do
-        expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
+        expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@status)
         @provider.load_current_resource
         expect(@provider.instance_eval("@is_resource_group")).to be_truthy
       end
     end
 
-    context "when there service is a subsytem" do
+    context "when the service is a subsystem" do
       before do
-        @status = double("Status", :exitstatus => 0, :stdout => "chef chef123 inoperative\n")
+        @group_status = double("Status", :exitstatus => 1, :stdout => "0513-086 The chef Group is not on file.\n")
+        @service_status = double("Status", :exitstatus => 0, :stdout => "chef chef inoperative\n")
       end
 
       it "service is a subsystem" do
-        expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
+        expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@group_status)
+        expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@service_status)
         @provider.load_current_resource
         expect(@provider.instance_eval("@is_resource_group")).to be_falsey
       end
diff --git a/spec/unit/provider/service/freebsd_service_spec.rb b/spec/unit/provider/service/freebsd_service_spec.rb
index 5a55425..cfc28c9 100644
--- a/spec/unit/provider/service/freebsd_service_spec.rb
+++ b/spec/unit/provider/service/freebsd_service_spec.rb
@@ -189,18 +189,6 @@ PS_SAMPLE
         expect(provider.status_load_success).to be_nil
       end
 
-      context "when ps command is nil" do
-        before do
-          node.automatic_attrs[:command] = {:ps => nil}
-        end
-
-        it "should set running to nil" do
-          pending "superclass raises no conversion of nil to string which seems broken"
-          provider.determine_current_status!
-          expect(current_resource.running).to be_nil
-        end
-      end
-
       context "when ps is empty string" do
         before do
           node.automatic_attrs[:command] = {:ps => ""}
diff --git a/spec/unit/provider/service/gentoo_service_spec.rb b/spec/unit/provider/service/gentoo_service_spec.rb
index c08982a..0aa7bf4 100644
--- a/spec/unit/provider/service/gentoo_service_spec.rb
+++ b/spec/unit/provider/service/gentoo_service_spec.rb
@@ -1,7 +1,7 @@
 #
 # Author:: Lee Jensen (<ljensen at engineyard.com>)
 # Author:: AJ Christensen (<aj at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -108,17 +108,17 @@ describe Chef::Provider::Service::Gentoo do
 
     it "should support the status command automatically" do
       @provider.load_current_resource
-      expect(@new_resource.supports[:status]).to be_truthy
+      expect(@provider.supports[:status]).to be true
     end
 
     it "should support the restart command automatically" do
       @provider.load_current_resource
-      expect(@new_resource.supports[:restart]).to be_truthy
+      expect(@provider.supports[:restart]).to be true
     end
 
     it "should not support the reload command automatically" do
       @provider.load_current_resource
-      expect(@new_resource.supports[:reload]).not_to be_truthy
+      expect(@provider.supports[:reload]).to be_falsey
     end
 
   end
diff --git a/spec/unit/provider/service/macosx_spec.rb b/spec/unit/provider/service/macosx_spec.rb
index 597845a..54183bd 100644
--- a/spec/unit/provider/service/macosx_spec.rb
+++ b/spec/unit/provider/service/macosx_spec.rb
@@ -60,15 +60,15 @@ XML
 
     ["Daemon", "Agent"].each do |service_type|
       ["redis-server", "io.redis.redis-server"].each do |service_name|
-        ["10.9", "10.10"].each do |platform_version|
+        ["10.9", "10.10", "10.11"].each do |platform_version|
           let(:plist) {'/Library/LaunchDaemons/io.redis.redis-server.plist'}
           let(:session) { StringIO.new }
           if service_type == 'Agent'
             let(:plist) {'/Library/LaunchAgents/io.redis.redis-server.plist'}
             let(:session) {'-S Aqua '}
-            let(:su_cmd) {'su igor -c'}
-            if platform_version != "10.10"
-              let(:su_cmd) {'su -l igor -c'}
+            let(:su_cmd) {'su -l igor -c'}
+            if platform_version == "10.9"
+              let(:su_cmd) {'su igor -c'}
             end
           end
           let(:service_label) {'io.redis.redis-server'}
diff --git a/spec/unit/provider/service/openbsd_service_spec.rb b/spec/unit/provider/service/openbsd_service_spec.rb
index 1b52064..d3c150a 100644
--- a/spec/unit/provider/service/openbsd_service_spec.rb
+++ b/spec/unit/provider/service/openbsd_service_spec.rb
@@ -35,10 +35,12 @@ describe Chef::Provider::Service::Openbsd do
     node
   end
 
+  let(:supports) { {:status => false} }
+
   let(:new_resource) do
     new_resource = Chef::Resource::Service.new("sndiod")
     new_resource.pattern("sndiod")
-    new_resource.supports({:status => false})
+    new_resource.supports(supports)
     new_resource
   end
 
@@ -106,9 +108,7 @@ describe Chef::Provider::Service::Openbsd do
     context "when the service supports status" do
       let(:status) { double(:stdout => "", :exitstatus => 0) }
 
-      before do
-        new_resource.supports({:status => true})
-      end
+      let(:supports) { { :status => true } }
 
       it "should run '/etc/rc.d/service_name status'" do
         expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_return(status)
@@ -305,10 +305,12 @@ describe Chef::Provider::Service::Openbsd do
     end
 
     describe Chef::Provider::Service::Openbsd, "restart_service" do
-      it "should call 'restart' on the service_name if the resource supports it" do
-        new_resource.supports({:restart => true})
-        expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} restart")
-        provider.restart_service()
+      context "when the new_resource supports restart" do
+        let(:supports) { { restart: true } }
+        it "should call 'restart' on the service_name if the resource supports it" do
+          expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} restart")
+          provider.restart_service()
+        end
       end
 
       it "should call the restart_command if one has been specified" do
diff --git a/spec/unit/provider/service/redhat_spec.rb b/spec/unit/provider/service/redhat_spec.rb
index 73cfec8..5aaf54d 100644
--- a/spec/unit/provider/service/redhat_spec.rb
+++ b/spec/unit/provider/service/redhat_spec.rb
@@ -64,24 +64,76 @@ describe "Chef::Provider::Service::Redhat" do
     end
 
     describe "load current resource" do
-      it "sets the current enabled status to true if the service is enabled for any run level" do
+      before do
         status = double("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
-        expect(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status)
+        allow(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status)
+      end
+
+      it "sets supports[:status] to true by default" do
         chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef    0:off   1:off   2:off   3:off   4:off   5:on  6:off", :stderr => "")
         expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
-        expect(@provider.instance_variable_get("@service_missing")).to be_falsey
+        expect(@provider.service_missing).to be false
         @provider.load_current_resource
-        expect(@current_resource.enabled).to be_truthy
+        expect(@provider.supports[:status]).to be true
+      end
+
+      it "lets the user override supports[:status] in the new_resource" do
+        @new_resource.supports( { status: false } )
+        @new_resource.pattern "myservice"
+        chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef    0:off   1:off   2:off   3:off   4:off   5:on  6:off", :stderr => "")
+        expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+        foo_out = double("ps_command", :exitstatus => 0, :stdout => "a line that matches myservice", :stderr => "")
+        expect(@provider).to receive(:shell_out!).with("foo").and_return(foo_out)
+        expect(@provider.service_missing).to be false
+        expect(@provider).not_to receive(:shell_out).with("/sbin/service chef status")
+        @provider.load_current_resource
+        expect(@provider.supports[:status]).to be false
+      end
+
+      it "sets the current enabled status to true if the service is enabled for any run level" do
+        chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef    0:off   1:off   2:off   3:off   4:off   5:on  6:off", :stderr => "")
+        expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+        expect(@provider.service_missing).to be false
+        @provider.load_current_resource
+        expect(@current_resource.enabled).to be true
       end
 
       it "sets the current enabled status to false if the regex does not match" do
-        status = double("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
-        expect(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status)
         chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef    0:off   1:off   2:off   3:off   4:off   5:off   6:off", :stderr => "")
         expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
-        expect(@provider.instance_variable_get("@service_missing")).to be_falsey
+        expect(@provider.service_missing).to be false
         expect(@provider.load_current_resource).to eql(@current_resource)
-        expect(@current_resource.enabled).to be_falsey
+        expect(@current_resource.enabled).to be false
+      end
+
+      it "sets the current enabled status to true if the service is enabled at specified run levels" do
+        @new_resource.run_levels([1, 2])
+        chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef    0:off   1:on   2:on   3:off   4:off   5:off   6:off", :stderr => "")
+        expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+        expect(@provider.service_missing).to be false
+        @provider.load_current_resource
+        expect(@current_resource.enabled).to be true
+        expect(@provider.current_run_levels).to eql([1, 2])
+      end
+
+      it "sets the current enabled status to false if the service is enabled at a run level it should not" do
+        @new_resource.run_levels([1, 2])
+        chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef    0:off   1:on   2:on   3:on   4:off   5:off   6:off", :stderr => "")
+        expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+        expect(@provider.service_missing).to be false
+        @provider.load_current_resource
+        expect(@current_resource.enabled).to be false
+        expect(@provider.current_run_levels).to eql([1, 2, 3])
+      end
+
+      it "sets the current enabled status to false if the service is not enabled at specified run levels" do
+        @new_resource.run_levels([ 2 ])
+        chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef    0:off   1:on   2:off   3:off   4:off   5:off   6:off", :stderr => "")
+        expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+        expect(@provider.service_missing).to be false
+        @provider.load_current_resource
+        expect(@current_resource.enabled).to be false
+        expect(@provider.current_run_levels).to eql([1])
       end
     end
 
@@ -144,6 +196,28 @@ describe "Chef::Provider::Service::Redhat" do
       expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} on")
       @provider.enable_service
     end
+
+    it "should call chkconfig to add 'service_name' at specified run_levels" do
+      allow(@provider).to receive(:run_levels).and_return([1, 2])
+      expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} on")
+      @provider.enable_service
+    end
+
+    it "should call chkconfig to add 'service_name' at specified run_levels when run_levels do not match" do
+      allow(@provider).to receive(:run_levels).and_return([1, 2])
+      allow(@provider).to receive(:current_run_levels).and_return([1, 3])
+      expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} on")
+      expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 3 #{@new_resource.service_name} off")
+      @provider.enable_service
+    end
+
+    it "should call chkconfig to add 'service_name' at specified run_levels if there is an extra run_level" do
+      allow(@provider).to receive(:run_levels).and_return([1, 2])
+      allow(@provider).to receive(:current_run_levels).and_return([1, 2, 3])
+      expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} on")
+      expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 3 #{@new_resource.service_name} off")
+      @provider.enable_service
+    end
   end
 
   describe "disable_service" do
@@ -151,6 +225,12 @@ describe "Chef::Provider::Service::Redhat" do
       expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} off")
       @provider.disable_service
     end
+
+    it "should call chkconfig to del 'service_name' at specified run_levels" do
+      allow(@provider).to receive(:run_levels).and_return([1, 2])
+      expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} off")
+      @provider.disable_service
+    end
   end
 
 end
diff --git a/spec/unit/provider/service/solaris_smf_service_spec.rb b/spec/unit/provider/service/solaris_smf_service_spec.rb
index 2039408..62c3ac6 100644
--- a/spec/unit/provider/service/solaris_smf_service_spec.rb
+++ b/spec/unit/provider/service/solaris_smf_service_spec.rb
@@ -31,66 +31,126 @@ describe Chef::Provider::Service::Solaris do
     @provider = Chef::Provider::Service::Solaris.new(@new_resource, @run_context)
     allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource)
 
-    @stdin = StringIO.new
-    @stdout = StringIO.new
-    @stderr = StringIO.new
-    @pid = 2342
-    @stdout_string = "state disabled"
-    allow(@stdout).to receive(:gets).and_return(@stdout_string)
-    @status = double("Status", :exitstatus => 0, :stdout => @stdout)
-    allow(@provider).to receive(:shell_out!).and_return(@status)
+    # enabled / started service (svcs -l chef)
+    enabled_svc_stdout = [
+      'fmri         svc:/application/chef:default',
+      'name         chef service',
+      'enabled      true',
+      'state        online',
+      'next_state   none',
+      'state_time   April  2, 2015 04:25:19 PM EDT',
+      'logfile      /var/svc/log/application-chef:default.log',
+      'restarter    svc:/system/svc/restarter:default',
+      'contract_id  1115271',
+      'dependency   require_all/error svc:/milestone/multi-user:default (online)'
+    ].join("\n")
+
+    # disabled / stopped service (svcs -l chef)
+    disabled_svc_stdout = [
+      'fmri         svc:/application/chef:default',
+      'name         chef service',
+      'enabled      false',
+      'state        disabled',
+      'next_state   none',
+      'state_time   April  2, 2015 04:25:19 PM EDT',
+      'logfile      /var/svc/log/application-chef:default.log',
+      'restarter    svc:/system/svc/restarter:default',
+      'contract_id  1115271',
+      'dependency   require_all/error svc:/milestone/multi-user:default (online)'
+    ].join("\n")
+
+    # disabled / stopped service (svcs -l chef)
+    maintenance_svc_stdout = [
+      'fmri         svc:/application/chef:default',
+      'name         chef service',
+      'enabled      true',
+      'state        maintenance',
+      'next_state   none',
+      'state_time   April  2, 2015 04:25:19 PM EDT',
+      'logfile      /var/svc/log/application-chef:default.log',
+      'restarter    svc:/system/svc/restarter:default',
+      'contract_id  1115271',
+      'dependency   require_all/error svc:/milestone/multi-user:default (online)'
+    ].join("\n")
+
+    # shell_out! return value for a service that is running
+    @enabled_svc_status = double("Status", :exitstatus => 0, :stdout => enabled_svc_stdout, :stdin => '', :stderr => '')
+
+    # shell_out! return value for a service that is disabled
+    @disabled_svc_status = double("Status", :exitstatus => 0, :stdout => disabled_svc_stdout, :stdin => '', :stderr => '')
+
+    # shell_out! return value for a service that is in maintenance mode
+    @maintenance_svc_status = double("Status", :exitstatus => 0, :stdout => maintenance_svc_stdout, :stdin => '', :stderr => '')
+
+    # shell_out! return value for a service that does not exist
+    @no_svc_status = double("Status", :exitstatus => 1, :stdout => '', :stdin => '', :stderr => "svcs: Pattern 'chef' doesn't match any instances\n")
+
+    # shell_out! return value for a successful execution
+    @success = double("clear", :exitstatus => 0, :stdout => '', :stdin => '', :stderr => '')
   end
 
-  it "should raise an error if /bin/svcs does not exist" do
-    expect(File).to receive(:exists?).with("/bin/svcs").and_return(false)
+  it "should raise an error if /bin/svcs and /usr/sbin/svcadm are not executable" do
+    allow(File).to receive(:executable?).with("/bin/svcs").and_return(false)
+    allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(false)
     expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service)
   end
 
-  describe "on a host with /bin/svcs" do
+  it "should raise an error if /bin/svcs is not executable" do
+    allow(File).to receive(:executable?).with("/bin/svcs").and_return(false)
+    allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(true)
+    expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service)
+  end
+
+  it "should raise an error if /usr/sbin/svcadm is not executable" do
+    allow(File).to receive(:executable?).with("/bin/svcs").and_return(true)
+    allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(false)
+    expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service)
+  end
+
+  describe "on a host with /bin/svcs and /usr/sbin/svcadm" do
 
     before do
-      allow(File).to receive(:exists?).with('/bin/svcs').and_return(true)
+      allow(File).to receive(:executable?).with("/bin/svcs").and_return(true)
+      allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(true)
     end
 
     describe "when discovering the current service state" do
       it "should create a current resource with the name of the new resource" do
-        allow(@provider).to receive(:shell_out!).with("/bin/svcs -l chef").and_return(@status)
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status)
         expect(Chef::Resource::Service).to receive(:new).and_return(@current_resource)
         @provider.load_current_resource
       end
 
       it "should return the current resource" do
-        allow(@provider).to receive(:shell_out!).with("/bin/svcs -l chef").and_return(@status)
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status)
         expect(@provider.load_current_resource).to eql(@current_resource)
       end
 
       it "should call '/bin/svcs -l service_name'" do
-        expect(@provider).to receive(:shell_out!).with("/bin/svcs -l chef", {:returns=>[0, 1]}).and_return(@status)
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status)
         @provider.load_current_resource
       end
 
       it "should mark service as not running" do
-        allow(@provider).to receive(:shell_out!).and_return(@status)
+        expect(@provider).to receive(:shell_out!).and_return(@disabled_svc_status)
         expect(@current_resource).to receive(:running).with(false)
         @provider.load_current_resource
       end
 
       it "should mark service as running" do
-        @status = double("Status", :exitstatus => 0, :stdout => 'state online')
-        allow(@provider).to receive(:shell_out!).and_return(@status)
+        expect(@provider).to receive(:shell_out!).and_return(@enabled_svc_status)
         expect(@current_resource).to receive(:running).with(true)
         @provider.load_current_resource
       end
 
       it "should not mark service as maintenance" do
-        allow(@provider).to receive(:shell_out!).and_return(@status)
+        expect(@provider).to receive(:shell_out!).and_return(@enabled_svc_status)
         @provider.load_current_resource
         expect(@provider.maintenance).to be_falsey
       end
 
       it "should mark service as maintenance" do
-        @status = double("Status", :exitstatus => 0, :stdout => 'state maintenance')
-        allow(@provider).to receive(:shell_out!).and_return(@status)
+        expect(@provider).to receive(:shell_out!).and_return(@maintenance_svc_status)
         @provider.load_current_resource
         expect(@provider.maintenance).to be_truthy
       end
@@ -99,30 +159,41 @@ describe Chef::Provider::Service::Solaris do
     describe "when enabling the service" do
       before(:each) do
         @provider.current_resource = @current_resource
-        @current_resource.enabled(true)
       end
 
       it "should call svcadm enable -s chef" do
-        expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}")
-        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status)
+        expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm", "clear", @current_resource.service_name)
+        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "enable", "-s", @current_resource.service_name).and_return(@success)
+        @provider.load_current_resource
+
         expect(@provider.enable_service).to be_truthy
         expect(@current_resource.enabled).to be_truthy
       end
 
       it "should call svcadm enable -s chef for start_service" do
-        expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}")
-        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status)
+        expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm", "clear", @current_resource.service_name)
+        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "enable", "-s", @current_resource.service_name).and_return(@success)
+        @provider.load_current_resource
         expect(@provider.start_service).to be_truthy
         expect(@current_resource.enabled).to be_truthy
       end
 
       it "should call svcadm clear chef for start_service when state maintenance" do
-        @status = double("Status", :exitstatus => 0, :stdout => 'state maintenance')
-        allow(@provider).to receive(:shell_out!).and_return(@status)
+        # we are in maint mode
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@maintenance_svc_status)
+        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "clear", @current_resource.service_name).and_return(@success)
+        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "enable", "-s", @current_resource.service_name).and_return(@success)
+
+        # load the resource, then enable it
         @provider.load_current_resource
-        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}").and_return(@status)
-        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
         expect(@provider.enable_service).to be_truthy
+
+        # now we are enabled
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status)
+        @provider.load_current_resource
+
         expect(@current_resource.enabled).to be_truthy
       end
     end
@@ -130,17 +201,20 @@ describe Chef::Provider::Service::Solaris do
     describe "when disabling the service" do
       before(:each) do
         @provider.current_resource = @current_resource
-        @current_resource.enabled(false)
       end
 
       it "should call svcadm disable -s chef" do
-        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm disable -s chef").and_return(@status)
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@disabled_svc_status)
+        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "disable", "-s", "chef").and_return(@success)
+        @provider.load_current_resource
         expect(@provider.disable_service).to be_truthy
         expect(@current_resource.enabled).to be_falsey
       end
 
       it "should call svcadm disable -s chef for stop_service" do
-        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm disable -s chef").and_return(@status)
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@disabled_svc_status)
+        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "disable", "-s", "chef").and_return(@success)
+        @provider.load_current_resource
         expect(@provider.stop_service).to be_truthy
         expect(@current_resource.enabled).to be_falsey
       end
@@ -149,12 +223,12 @@ describe Chef::Provider::Service::Solaris do
 
     describe "when reloading the service" do
       before(:each) do
-        @status = double("Process::Status", :exitstatus => 0)
         @provider.current_resource = @current_resource
+        allow(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status)
       end
 
       it "should call svcadm refresh chef" do
-        expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/svcadm refresh chef").and_return(@status)
+        expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "refresh", "chef")
         @provider.reload_service
       end
 
@@ -162,19 +236,16 @@ describe Chef::Provider::Service::Solaris do
 
     describe "when the service doesn't exist" do
       before(:each) do
-        @stdout_string = ""
-        @status = double("Status", :exitstatus => 1, :stdout => @stdout)
         @provider.current_resource = @current_resource
+        expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@no_svc_status)
       end
 
       it "should be marked not running" do
-        expect(@provider).to receive(:shell_out!).with("/bin/svcs -l chef", {:returns=>[0, 1]}).and_return(@status)
         @provider.service_status
         expect(@current_resource.running).to be_falsey
       end
 
       it "should be marked not enabled" do
-        expect(@provider).to receive(:shell_out!).with("/bin/svcs -l chef", {:returns=>[0, 1]}).and_return(@status)
         @provider.service_status
         expect(@current_resource.enabled).to be_falsey
       end
diff --git a/spec/unit/provider/service/upstart_service_spec.rb b/spec/unit/provider/service/upstart_service_spec.rb
index ca7ce8f..6fb1f9f 100644
--- a/spec/unit/provider/service/upstart_service_spec.rb
+++ b/spec/unit/provider/service/upstart_service_spec.rb
@@ -19,6 +19,10 @@
 require 'spec_helper'
 
 describe Chef::Provider::Service::Upstart do
+  let(:shell_out_success) do
+    double('shell_out_with_systems_locale', :exitstatus => 0, :error? => false)
+  end
+
   before(:each) do
     @node =Chef::Node.new
     @node.name('upstarter')
@@ -119,6 +123,25 @@ describe Chef::Provider::Service::Upstart do
       end
     end
 
+    describe "when the status command uses the new format with an instance" do
+      before do
+      end
+
+      it "should set running to true if the status command returns 0" do
+        @stdout = StringIO.new("rsyslog (test) start/running, process 100")
+        allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @provider.load_current_resource
+        expect(@current_resource.running).to be_truthy
+      end
+
+      it "should set running to false if the status command returns anything except 0" do
+        @stdout = StringIO.new("rsyslog (test) stop/waiting, process 100")
+        allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @provider.load_current_resource
+        expect(@current_resource.running).to be_falsey
+      end
+    end
+
     describe "when the status command uses the old format" do
       it "should set running to true if the status command returns 0" do
         @stdout = StringIO.new("rsyslog (start) running, process 32225")
@@ -173,7 +196,7 @@ describe Chef::Provider::Service::Upstart do
       end
 
       it "should run the services status command if one has been specified" do
-        allow(@provider).to receive(:shell_out!).with("/bin/chefhasmonkeypants status").and_return(0)
+        allow(@provider).to receive(:shell_out!).with("/bin/chefhasmonkeypants status").and_return(shell_out_success)
         expect(@current_resource).to receive(:running).with(true)
         @provider.load_current_resource
       end
@@ -246,7 +269,7 @@ describe Chef::Provider::Service::Upstart do
     end
 
     it "should call '/sbin/start service_name' if no start command is specified" do
-      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(0)
+      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(shell_out_success)
       @provider.start_service()
     end
 
@@ -261,7 +284,7 @@ describe Chef::Provider::Service::Upstart do
       @new_resource.parameters({ "OSD_ID" => "2" })
       @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
       @provider.current_resource = @current_resource
-      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start rsyslog OSD_ID=2").and_return(0)
+      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start rsyslog OSD_ID=2").and_return(shell_out_success)
       @provider.start_service()
     end
 
@@ -274,13 +297,13 @@ describe Chef::Provider::Service::Upstart do
 
     it "should call '/sbin/restart service_name' if no restart command is specified" do
       allow(@current_resource).to receive(:running).and_return(true)
-      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/restart #{@new_resource.service_name}").and_return(0)
+      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/restart #{@new_resource.service_name}").and_return(shell_out_success)
       @provider.restart_service()
     end
 
     it "should call '/sbin/start service_name' if restart_service is called for a stopped service" do
       allow(@current_resource).to receive(:running).and_return(false)
-      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(0)
+      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(shell_out_success)
       @provider.restart_service()
     end
 
@@ -293,7 +316,7 @@ describe Chef::Provider::Service::Upstart do
 
     it "should call '/sbin/reload service_name' if no reload command is specified" do
       allow(@current_resource).to receive(:running).and_return(true)
-      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/reload #{@new_resource.service_name}").and_return(0)
+      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/reload #{@new_resource.service_name}").and_return(shell_out_success)
       @provider.reload_service()
     end
 
@@ -306,7 +329,7 @@ describe Chef::Provider::Service::Upstart do
 
     it "should call '/sbin/stop service_name' if no stop command is specified" do
       allow(@current_resource).to receive(:running).and_return(true)
-      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/stop #{@new_resource.service_name}").and_return(0)
+      expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/stop #{@new_resource.service_name}").and_return(shell_out_success)
       @provider.stop_service()
     end
 
diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb
index 784a223..4c9f5b3 100644
--- a/spec/unit/provider/service/windows_spec.rb
+++ b/spec/unit/provider/service/windows_spec.rb
@@ -21,369 +21,380 @@ require 'spec_helper'
 require 'mixlib/shellout'
 
 describe Chef::Provider::Service::Windows, "load_current_resource" do
-  before(:each) do
-    @node = Chef::Node.new
-    @events = Chef::EventDispatch::Dispatcher.new
-    @run_context = Chef::RunContext.new(@node, {}, @events)
-    @new_resource = Chef::Resource::WindowsService.new("chef")
-    @provider = Chef::Provider::Service::Windows.new(@new_resource, @run_context)
-    @provider.current_resource = Chef::Resource::WindowsService.new("current-chef")
-    Object.send(:remove_const, 'Win32') if defined?(Win32)
-    Win32 = Module.new
+  include_context "Win32"
+
+  let(:new_resource) { Chef::Resource::WindowsService.new("chef") }
+  let(:provider) do
+    prvdr = Chef::Provider::Service::Windows.new(new_resource,
+      Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new))
+    prvdr.current_resource = Chef::Resource::WindowsService.new("current-chef")
+    prvdr
+  end
+
+  before(:all) do
     Win32::Service = Class.new
+  end
+
+  before(:each) do
     Win32::Service::AUTO_START = 0x00000002
     Win32::Service::DEMAND_START = 0x00000003
     Win32::Service::DISABLED = 0x00000004
-    allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+
+    allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
       double("StatusStruct", :current_state => "running"))
-    allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return(
+    allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
       double("ConfigStruct", :start_type => "auto start"))
     allow(Win32::Service).to receive(:exists?).and_return(true)
     allow(Win32::Service).to receive(:configure).and_return(Win32::Service)
   end
 
-  it "should set the current resources service name to the new resources service name" do
-    @provider.load_current_resource
-    expect(@provider.current_resource.service_name).to eq('chef')
+  after(:each) do
+    Win32::Service.send(:remove_const, 'AUTO_START') if defined?(Win32::Service::AUTO_START)
+    Win32::Service.send(:remove_const, 'DEMAND_START') if defined?(Win32::Service::DEMAND_START)
+    Win32::Service.send(:remove_const, 'DISABLED') if defined?(Win32::Service::DISABLED)
   end
 
-  it "should return the current resource" do
-    expect(@provider.load_current_resource).to equal(@provider.current_resource)
+  it "sets the current resources service name to the new resources service name" do
+    provider.load_current_resource
+    expect(provider.current_resource.service_name).to eq('chef')
   end
 
-  it "should set the current resources status" do
-    @provider.load_current_resource
-    expect(@provider.current_resource.running).to be_truthy
+  it "returns the current resource" do
+    expect(provider.load_current_resource).to equal(provider.current_resource)
   end
 
-  it "should set the current resources start type" do
-    @provider.load_current_resource
-    expect(@provider.current_resource.enabled).to be_truthy
+  it "sets the current resources status" do
+    provider.load_current_resource
+    expect(provider.current_resource.running).to be_truthy
+  end
+
+  it "sets the current resources start type" do
+    provider.load_current_resource
+    expect(provider.current_resource.enabled).to be_truthy
   end
 
   it "does not set the current resources start type if it is neither AUTO START or DISABLED" do
-    allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return(
+    allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
       double("ConfigStruct", :start_type => "manual"))
-    @provider.load_current_resource
-    expect(@provider.current_resource.enabled).to be_nil
+    provider.load_current_resource
+    expect(provider.current_resource.enabled).to be_nil
   end
 
   describe Chef::Provider::Service::Windows, "start_service" do
     before(:each) do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "stopped"),
         double("StatusStruct", :current_state => "running"))
     end
 
-    it "should call the start command if one is specified" do
-      @new_resource.start_command "sc start chef"
-      expect(@provider).to receive(:shell_out!).with("#{@new_resource.start_command}").and_return("Starting custom service")
-      @provider.start_service
-      expect(@new_resource.updated_by_last_action?).to be_truthy
+    it "calls the start command if one is specified" do
+      new_resource.start_command "sc start chef"
+      expect(provider).to receive(:shell_out!).with("#{new_resource.start_command}").and_return("Starting custom service")
+      provider.start_service
+      expect(new_resource.updated_by_last_action?).to be_truthy
     end
 
-    it "should use the built-in command if no start command is specified" do
-      expect(Win32::Service).to receive(:start).with(@new_resource.service_name)
-      @provider.start_service
-      expect(@new_resource.updated_by_last_action?).to be_truthy
+    it "uses the built-in command if no start command is specified" do
+      expect(Win32::Service).to receive(:start).with(new_resource.service_name)
+      provider.start_service
+      expect(new_resource.updated_by_last_action?).to be_truthy
     end
 
-    it "should do nothing if the service does not exist" do
-      allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false)
-      expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name)
-      @provider.start_service
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+    it "does nothing if the service does not exist" do
+      allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false)
+      expect(Win32::Service).not_to receive(:start).with(new_resource.service_name)
+      provider.start_service
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
-    it "should do nothing if the service is running" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "does nothing if the service is running" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "running"))
-      @provider.load_current_resource
-      expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name)
-      @provider.start_service
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.load_current_resource
+      expect(Win32::Service).not_to receive(:start).with(new_resource.service_name)
+      provider.start_service
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
-    it "should raise an error if the service is paused" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "raises an error if the service is paused" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "paused"))
-      @provider.load_current_resource
-      expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name)
-      expect { @provider.start_service }.to raise_error( Chef::Exceptions::Service )
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.load_current_resource
+      expect(Win32::Service).not_to receive(:start).with(new_resource.service_name)
+      expect { provider.start_service }.to raise_error( Chef::Exceptions::Service )
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
-    it "should wait and continue if the service is in start_pending" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "waits and continues if the service is in start_pending" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "start pending"),
         double("StatusStruct", :current_state => "start pending"),
         double("StatusStruct", :current_state => "running"))
-      @provider.load_current_resource
-      expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name)
-      @provider.start_service
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.load_current_resource
+      expect(Win32::Service).not_to receive(:start).with(new_resource.service_name)
+      provider.start_service
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
-    it "should fail if the service is in stop_pending" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "fails if the service is in stop_pending" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "stop pending"))
-      @provider.load_current_resource
-      expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name)
-      expect { @provider.start_service }.to raise_error( Chef::Exceptions::Service )
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.load_current_resource
+      expect(Win32::Service).not_to receive(:start).with(new_resource.service_name)
+      expect { provider.start_service }.to raise_error( Chef::Exceptions::Service )
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
     describe "running as a different account" do
-      let(:old_run_as_user) { @new_resource.run_as_user }
-      let(:old_run_as_password) { @new_resource.run_as_password }
+      let(:old_run_as_user) { new_resource.run_as_user }
+      let(:old_run_as_password) { new_resource.run_as_password }
 
       before {
-        @new_resource.run_as_user(".\\wallace")
-        @new_resource.run_as_password("Wensleydale")
+        new_resource.run_as_user(".\\wallace")
+        new_resource.run_as_password("Wensleydale")
       }
 
       after {
-        @new_resource.run_as_user(old_run_as_user)
-        @new_resource.run_as_password(old_run_as_password)
+        new_resource.run_as_user(old_run_as_user)
+        new_resource.run_as_password(old_run_as_password)
       }
 
-      it "should call #grant_service_logon if the :run_as_user and :run_as_password attributes are present" do
+      it "calls #grant_service_logon if the :run_as_user and :run_as_password attributes are present" do
         expect(Win32::Service).to receive(:start)
-        expect(@provider).to receive(:grant_service_logon).and_return(true)
-        @provider.start_service
+        expect(provider).to receive(:grant_service_logon).and_return(true)
+        provider.start_service
       end
     end
   end
 
-
   describe Chef::Provider::Service::Windows, "stop_service" do
 
     before(:each) do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "running"),
         double("StatusStruct", :current_state => "stopped"))
     end
 
-    it "should call the stop command if one is specified" do
-      @new_resource.stop_command "sc stop chef"
-      expect(@provider).to receive(:shell_out!).with("#{@new_resource.stop_command}").and_return("Stopping custom service")
-      @provider.stop_service
-      expect(@new_resource.updated_by_last_action?).to be_truthy
+    it "calls the stop command if one is specified" do
+      new_resource.stop_command "sc stop chef"
+      expect(provider).to receive(:shell_out!).with("#{new_resource.stop_command}").and_return("Stopping custom service")
+      provider.stop_service
+      expect(new_resource.updated_by_last_action?).to be_truthy
     end
 
-    it "should use the built-in command if no stop command is specified" do
-      expect(Win32::Service).to receive(:stop).with(@new_resource.service_name)
-      @provider.stop_service
-      expect(@new_resource.updated_by_last_action?).to be_truthy
+    it "uses the built-in command if no stop command is specified" do
+      expect(Win32::Service).to receive(:stop).with(new_resource.service_name)
+      provider.stop_service
+      expect(new_resource.updated_by_last_action?).to be_truthy
     end
 
-    it "should do nothing if the service does not exist" do
-      allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false)
-      expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name)
-      @provider.stop_service
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+    it "does nothing if the service does not exist" do
+      allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false)
+      expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name)
+      provider.stop_service
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
-    it "should do nothing if the service is stopped" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "does nothing if the service is stopped" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "stopped"))
-      @provider.load_current_resource
-      expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name)
-      @provider.stop_service
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.load_current_resource
+      expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name)
+      provider.stop_service
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
-    it "should raise an error if the service is paused" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "raises an error if the service is paused" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "paused"))
-      @provider.load_current_resource
-      expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name)
-      expect { @provider.stop_service }.to raise_error( Chef::Exceptions::Service )
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.load_current_resource
+      expect(Win32::Service).not_to receive(:start).with(new_resource.service_name)
+      expect { provider.stop_service }.to raise_error( Chef::Exceptions::Service )
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
-    it "should wait and continue if the service is in stop_pending" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "waits and continue if the service is in stop_pending" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "stop pending"),
         double("StatusStruct", :current_state => "stop pending"),
         double("StatusStruct", :current_state => "stopped"))
-      @provider.load_current_resource
-      expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name)
-      @provider.stop_service
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.load_current_resource
+      expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name)
+      provider.stop_service
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
-    it "should fail if the service is in start_pending" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "fails if the service is in start_pending" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "start pending"))
-      @provider.load_current_resource
-      expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name)
-      expect { @provider.stop_service }.to raise_error( Chef::Exceptions::Service )
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.load_current_resource
+      expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name)
+      expect { provider.stop_service }.to raise_error( Chef::Exceptions::Service )
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
-    it "should pass custom timeout to the stop command if provided" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "passes custom timeout to the stop command if provided" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "running"))
-      @new_resource.timeout 1
-      expect(Win32::Service).to receive(:stop).with(@new_resource.service_name)
+      new_resource.timeout 1
+      expect(Win32::Service).to receive(:stop).with(new_resource.service_name)
       Timeout.timeout(2) do
-        expect { @provider.stop_service }.to raise_error(Timeout::Error)
+        expect { provider.stop_service }.to raise_error(Timeout::Error)
       end
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
   end
 
   describe Chef::Provider::Service::Windows, "restart_service" do
 
-    it "should call the restart command if one is specified" do
-      @new_resource.restart_command "sc restart"
-      expect(@provider).to receive(:shell_out!).with("#{@new_resource.restart_command}")
-      @provider.restart_service
-      expect(@new_resource.updated_by_last_action?).to be_truthy
+    it "calls the restart command if one is specified" do
+      new_resource.restart_command "sc restart"
+      expect(provider).to receive(:shell_out!).with("#{new_resource.restart_command}")
+      provider.restart_service
+      expect(new_resource.updated_by_last_action?).to be_truthy
     end
 
-    it "should stop then start the service if it is running" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "stops then starts the service if it is running" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "running"),
         double("StatusStruct", :current_state => "stopped"),
         double("StatusStruct", :current_state => "stopped"),
         double("StatusStruct", :current_state => "running"))
-      expect(Win32::Service).to receive(:stop).with(@new_resource.service_name)
-      expect(Win32::Service).to receive(:start).with(@new_resource.service_name)
-      @provider.restart_service
-      expect(@new_resource.updated_by_last_action?).to be_truthy
+      expect(Win32::Service).to receive(:stop).with(new_resource.service_name)
+      expect(Win32::Service).to receive(:start).with(new_resource.service_name)
+      provider.restart_service
+      expect(new_resource.updated_by_last_action?).to be_truthy
     end
 
-    it "should just start the service if it is stopped" do
-      allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return(
+    it "just starts the service if it is stopped" do
+      allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
         double("StatusStruct", :current_state => "stopped"),
         double("StatusStruct", :current_state => "stopped"),
         double("StatusStruct", :current_state => "running"))
-      expect(Win32::Service).to receive(:start).with(@new_resource.service_name)
-      @provider.restart_service
-      expect(@new_resource.updated_by_last_action?).to be_truthy
+      expect(Win32::Service).to receive(:start).with(new_resource.service_name)
+      provider.restart_service
+      expect(new_resource.updated_by_last_action?).to be_truthy
     end
 
-    it "should do nothing if the service does not exist" do
-      allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false)
-      expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name)
-      expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name)
-      @provider.restart_service
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+    it "does nothing if the service does not exist" do
+      allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false)
+      expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name)
+      expect(Win32::Service).not_to receive(:start).with(new_resource.service_name)
+      provider.restart_service
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
 
   end
 
   describe Chef::Provider::Service::Windows, "enable_service" do
     before(:each) do
-      allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return(
+      allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
         double("ConfigStruct", :start_type => "disabled"))
     end
 
-    it "should enable service" do
-      expect(Win32::Service).to receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::AUTO_START)
-      @provider.enable_service
-      expect(@new_resource.updated_by_last_action?).to be_truthy
+    it "enables service" do
+      expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::AUTO_START)
+      provider.enable_service
+      expect(new_resource.updated_by_last_action?).to be_truthy
     end
 
-    it "should do nothing if the service does not exist" do
-      allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false)
+    it "does nothing if the service does not exist" do
+      allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false)
       expect(Win32::Service).not_to receive(:configure)
-      @provider.enable_service
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.enable_service
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
   end
 
   describe Chef::Provider::Service::Windows, "action_enable" do
     it "does nothing if the service is enabled" do
-      allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return(
+      allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
         double("ConfigStruct", :start_type => "auto start"))
-      expect(@provider).not_to receive(:enable_service)
-      @provider.action_enable
+      expect(provider).not_to receive(:enable_service)
+      provider.action_enable
     end
 
     it "enables the service if it is not set to automatic start" do
-      allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return(
+      allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
         double("ConfigStruct", :start_type => "disabled"))
-      expect(@provider).to receive(:enable_service)
-      @provider.action_enable
+      expect(provider).to receive(:enable_service)
+      provider.action_enable
     end
   end
 
   describe Chef::Provider::Service::Windows, "action_disable" do
     it "does nothing if the service is disabled" do
-      allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return(
+      allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
         double("ConfigStruct", :start_type => "disabled"))
-      expect(@provider).not_to receive(:disable_service)
-      @provider.action_disable
+      expect(provider).not_to receive(:disable_service)
+      provider.action_disable
     end
 
     it "disables the service if it is not set to disabled" do
-      allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return(
+      allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
         double("ConfigStruct", :start_type => "auto start"))
-      expect(@provider).to receive(:disable_service)
-      @provider.action_disable
+      expect(provider).to receive(:disable_service)
+      provider.action_disable
     end
   end
 
   describe Chef::Provider::Service::Windows, "disable_service" do
     before(:each) do
-      allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return(
+      allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
         double("ConfigStruct", :start_type => "auto start"))
     end
 
-    it "should disable service" do
+    it "disables service" do
       expect(Win32::Service).to receive(:configure)
-      @provider.disable_service
-      expect(@new_resource.updated_by_last_action?).to be_truthy
+      provider.disable_service
+      expect(new_resource.updated_by_last_action?).to be_truthy
     end
 
-    it "should do nothing if the service does not exist" do
-      allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false)
+    it "does nothing if the service does not exist" do
+      allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false)
       expect(Win32::Service).not_to receive(:configure)
-      @provider.disable_service
-      expect(@new_resource.updated_by_last_action?).to be_falsey
+      provider.disable_service
+      expect(new_resource.updated_by_last_action?).to be_falsey
     end
   end
 
   describe Chef::Provider::Service::Windows, "action_configure_startup" do
     { :automatic => "auto start", :manual => "demand start", :disabled => "disabled" }.each do |type,win32|
       it "sets the startup type to #{type} if it is something else" do
-        @new_resource.startup_type(type)
-        allow(@provider).to receive(:current_start_type).and_return("fire")
-        expect(@provider).to receive(:set_startup_type).with(type)
-        @provider.action_configure_startup
+        new_resource.startup_type(type)
+        allow(provider).to receive(:current_start_type).and_return("fire")
+        expect(provider).to receive(:set_startup_type).with(type)
+        provider.action_configure_startup
       end
 
       it "leaves the startup type as #{type} if it is already set" do
-        @new_resource.startup_type(type)
-        allow(@provider).to receive(:current_start_type).and_return(win32)
-        expect(@provider).not_to receive(:set_startup_type).with(type)
-        @provider.action_configure_startup
+        new_resource.startup_type(type)
+        allow(provider).to receive(:current_start_type).and_return(win32)
+        expect(provider).not_to receive(:set_startup_type).with(type)
+        provider.action_configure_startup
       end
     end
   end
 
   describe Chef::Provider::Service::Windows, "set_start_type" do
     it "when called with :automatic it calls Win32::Service#configure with Win32::Service::AUTO_START" do
-      expect(Win32::Service).to receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::AUTO_START)
-      @provider.send(:set_startup_type, :automatic)
+      expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::AUTO_START)
+      provider.send(:set_startup_type, :automatic)
     end
 
     it "when called with :manual it calls Win32::Service#configure with Win32::Service::DEMAND_START" do
-      expect(Win32::Service).to receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::DEMAND_START)
-      @provider.send(:set_startup_type, :manual)
+      expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::DEMAND_START)
+      provider.send(:set_startup_type, :manual)
     end
 
     it "when called with :disabled it calls Win32::Service#configure with Win32::Service::DISABLED" do
-      expect(Win32::Service).to receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::DISABLED)
-      @provider.send(:set_startup_type, :disabled)
+      expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::DISABLED)
+      provider.send(:set_startup_type, :disabled)
     end
 
     it "raises an exception when given an unknown start type" do
-      expect { @provider.send(:set_startup_type, :fire_truck) }.to raise_error(Chef::Exceptions::ConfigurationError)
+      expect { provider.send(:set_startup_type, :fire_truck) }.to raise_error(Chef::Exceptions::ConfigurationError)
     end
   end
 
@@ -409,9 +420,9 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
     let(:success_string) { "The task has completed successfully.\r\nSee logfile etc." }
     let(:failure_string) { "Look on my works, ye Mighty, and despair!" }
     let(:command) {
-      dbfile = @provider.grant_dbfile_name(username)
-      policyfile = @provider.grant_policyfile_name(username)
-      logfile = @provider.grant_logfile_name(username)
+      dbfile = provider.grant_dbfile_name(username)
+      policyfile = provider.grant_policyfile_name(username)
+      logfile = provider.grant_logfile_name(username)
 
       %Q{secedit.exe /configure /db "#{dbfile}" /cfg "#{policyfile}" /areas USER_RIGHTS SECURITYPOLICY SERVICES /log "#{logfile}"}
     }
@@ -424,20 +435,20 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
 
     after {
       # only needed for the second test.
-      ::File.delete(@provider.grant_policyfile_name(username)) rescue nil
-      ::File.delete(@provider.grant_logfile_name(username)) rescue nil
-      ::File.delete(@provider.grant_dbfile_name(username)) rescue nil
+      ::File.delete(provider.grant_policyfile_name(username)) rescue nil
+      ::File.delete(provider.grant_logfile_name(username)) rescue nil
+      ::File.delete(provider.grant_dbfile_name(username)) rescue nil
     }
 
     it "calls Mixlib::Shellout with the correct command string" do
       expect_any_instance_of(Mixlib::ShellOut).to receive(:exitstatus).and_return(0)
-      expect(@provider.grant_service_logon(username)).to equal true
+      expect(provider.grant_service_logon(username)).to equal true
     end
 
     it "raises an exception when the grant command fails" do
       expect_any_instance_of(Mixlib::ShellOut).to receive(:exitstatus).and_return(1)
       expect_any_instance_of(Mixlib::ShellOut).to receive(:stdout).and_return(failure_string)
-      expect { @provider.grant_service_logon(username) }.to raise_error(Chef::Exceptions::Service)
+      expect { provider.grant_service_logon(username) }.to raise_error(Chef::Exceptions::Service)
     end
   end
 
@@ -445,17 +456,17 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
     include_context "testing private methods"
 
     it "correctly reformats usernames to create valid filenames" do
-      expect(@provider.clean_username_for_path("\\\\problem username/oink.txt")).to eq("_problem_username_oink_txt")
-      expect(@provider.clean_username_for_path("boring_username")).to eq("boring_username")
+      expect(provider.clean_username_for_path("\\\\problem username/oink.txt")).to eq("_problem_username_oink_txt")
+      expect(provider.clean_username_for_path("boring_username")).to eq("boring_username")
     end
 
     it "correctly reformats usernames for the policy file" do
-      expect(@provider.canonicalize_username(".\\maryann")).to eq("maryann")
-      expect(@provider.canonicalize_username("maryann")).to eq("maryann")
+      expect(provider.canonicalize_username(".\\maryann")).to eq("maryann")
+      expect(provider.canonicalize_username("maryann")).to eq("maryann")
 
-      expect(@provider.canonicalize_username("\\\\maryann")).to eq("maryann")
-      expect(@provider.canonicalize_username("mydomain\\\\maryann")).to eq("mydomain\\\\maryann")
-      expect(@provider.canonicalize_username("\\\\mydomain\\\\maryann")).to eq("mydomain\\\\maryann")
+      expect(provider.canonicalize_username("\\\\maryann")).to eq("maryann")
+      expect(provider.canonicalize_username("mydomain\\\\maryann")).to eq("mydomain\\\\maryann")
+      expect(provider.canonicalize_username("\\\\mydomain\\\\maryann")).to eq("mydomain\\\\maryann")
     end
   end
 end
diff --git a/spec/unit/provider/subversion_spec.rb b/spec/unit/provider/subversion_spec.rb
index 9ca11b8..9d4a8bd 100644
--- a/spec/unit/provider/subversion_spec.rb
+++ b/spec/unit/provider/subversion_spec.rb
@@ -27,6 +27,7 @@ describe Chef::Provider::Subversion do
     @resource.revision "12345"
     @resource.svn_arguments(false)
     @resource.svn_info_args(false)
+    @resource.svn_binary "svn"
     @node = Chef::Node.new
     @events = Chef::EventDispatch::Dispatcher.new
     @run_context = Chef::RunContext.new(@node, {}, @events)
@@ -63,28 +64,18 @@ describe Chef::Provider::Subversion do
                           "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision
                           "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n"
       expect(::File).to receive(:exist?).at_least(1).times.with("/my/deploy/dir/.svn").and_return(true)
-      expect(::File).to receive(:directory?).with("/my/deploy/dir").and_return(true)
-      expect(::Dir).to receive(:chdir).with("/my/deploy/dir").and_yield
-      allow(@stdout).to receive(:string).and_return(example_svn_info)
-      allow(@stderr).to receive(:string).and_return("")
-      allow(@exitstatus).to receive(:exitstatus).and_return(0)
-      expected_command = ["svn info", {:cwd=>"/my/deploy/dir"}]
-      expect(@provider).to receive(:popen4).with(*expected_command).
-                                        and_yield("no-pid", "no-stdin", @stdout, at stderr).
-                                        and_return(@exitstatus)
+      expected_command = ["svn info", {:cwd => '/my/deploy/dir', :returns => [0,1]}]
+      expect(@provider).to receive(:shell_out!).with(*expected_command).
+                             and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => ""))
       expect(@provider.find_current_revision).to eql("11410")
     end
 
     it "gives nil as the current revision if the deploy dir isn't a SVN working copy" do
       example_svn_info = "svn: '/tmp/deploydir' is not a working copy\n"
       expect(::File).to receive(:exist?).with("/my/deploy/dir/.svn").and_return(true)
-      expect(::File).to receive(:directory?).with("/my/deploy/dir").and_return(true)
-      expect(::Dir).to receive(:chdir).with("/my/deploy/dir").and_yield
-      allow(@stdout).to receive(:string).and_return(example_svn_info)
-      allow(@stderr).to receive(:string).and_return("")
-      allow(@exitstatus).to receive(:exitstatus).and_return(1)
-      expect(@provider).to receive(:popen4).and_yield("no-pid", "no-stdin", @stdout, at stderr).
-                                        and_return(@exitstatus)
+      expected_command = ["svn info", {:cwd => '/my/deploy/dir', :returns => [0,1]}]
+      expect(@provider).to receive(:shell_out!).with(*expected_command).
+                             and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => ""))
       expect(@provider.find_current_revision).to be_nil
     end
 
@@ -127,28 +118,20 @@ describe Chef::Provider::Subversion do
                           "Last Changed Author: codeninja\n" +
                           "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision
                           "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n"
-      exitstatus = double("exitstatus")
-      allow(exitstatus).to receive(:exitstatus).and_return(0)
       @resource.revision "HEAD"
-      allow(@stdout).to receive(:string).and_return(example_svn_info)
-      allow(@stderr).to receive(:string).and_return("")
-      expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache  -rHEAD", {:cwd=>Dir.tmpdir}]
-      expect(@provider).to receive(:popen4).with(*expected_command).
-                                        and_yield("no-pid","no-stdin", at stdout, at stderr).
-                                        and_return(exitstatus)
+      expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache  -rHEAD", {:cwd => '/my/deploy/dir', :returns => [0,1]}]
+      expect(@provider).to receive(:shell_out!).with(*expected_command).
+                                        and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => ""))
       expect(@provider.revision_int).to eql("11410")
     end
 
     it "returns a helpful message if data from `svn info` can't be parsed" do
       example_svn_info =  "some random text from an error message\n"
-      exitstatus = double("exitstatus")
-      allow(exitstatus).to receive(:exitstatus).and_return(0)
       @resource.revision "HEAD"
-      allow(@stdout).to receive(:string).and_return(example_svn_info)
-      allow(@stderr).to receive(:string).and_return("")
-      expect(@provider).to receive(:popen4).and_yield("no-pid","no-stdin", at stdout, at stderr).
-                                        and_return(exitstatus)
-      expect {@provider.revision_int}.to raise_error(RuntimeError, "Could not parse `svn info` data: some random text from an error message")
+      expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache  -rHEAD", {:cwd => '/my/deploy/dir', :returns => [0,1]}]
+      expect(@provider).to receive(:shell_out!).with(*expected_command).
+                             and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => ""))
+      expect {@provider.revision_int}.to raise_error(RuntimeError, "Could not parse `svn info` data: some random text from an error message\n")
 
     end
 
@@ -277,4 +260,40 @@ describe Chef::Provider::Subversion do
     expect(@resource).to be_updated
   end
 
+  context "selects the correct svn binary" do
+    before do
+    end
+
+    it "selects 'svn' as the binary by default" do
+      @resource.svn_binary nil
+      allow(ChefConfig).to receive(:windows?) { false }
+      expect(@provider).to receive(:svn_binary).and_return('svn')
+      expect(@provider.export_command).to eql(
+        'svn export --force -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir')
+    end
+
+    it "selects an svn binary with an exe extension on windows" do
+      @resource.svn_binary nil
+      allow(ChefConfig).to receive(:windows?) { true }
+      expect(@provider).to receive(:svn_binary).and_return('svn.exe')
+      expect(@provider.export_command).to eql(
+        'svn.exe export --force -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir')
+    end
+
+    it "uses a custom svn binary as part of the svn command" do
+      @resource.svn_binary 'teapot'
+      expect(@provider).to receive(:svn_binary).and_return('teapot')
+      expect(@provider.export_command).to eql(
+        'teapot export --force -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir')
+    end
+
+    it "wraps custom svn binary with quotes if it contains whitespace" do
+      @resource.svn_binary 'c:/program files (x86)/subversion/svn.exe'
+      expect(@provider).to receive(:svn_binary).and_return('c:/program files (x86)/subversion/svn.exe')
+      expect(@provider.export_command).to eql(
+        '"c:/program files (x86)/subversion/svn.exe" export --force -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir')
+    end
+
+  end
+
 end
diff --git a/spec/unit/provider/template/content_spec.rb b/spec/unit/provider/template/content_spec.rb
index 4b88a3a..509c8cf 100644
--- a/spec/unit/provider/template/content_spec.rb
+++ b/spec/unit/provider/template/content_spec.rb
@@ -20,10 +20,24 @@ require 'spec_helper'
 
 describe Chef::Provider::Template::Content do
 
+  let(:enclosing_directory) {
+    canonicalize_path(Dir.mktmpdir)
+  }
+
+  let(:resource_path) {
+    canonicalize_path(File.expand_path(File.join(enclosing_directory, "openldap_stuff.conf")))
+  }
+
   let(:new_resource) do
     double("Chef::Resource::Template (new)",
          :cookbook_name => 'openldap',
+         :recipe_name => 'default',
+         :source_line => "/Users/lamont/solo/cookbooks/openldap/recipes/default.rb:2:in `from_file'",
+         :source_line_file => "/Users/lamont/solo/cookbooks/openldap/recipes/default.rb",
+         :source_line_number => "2",
          :source => 'openldap_stuff.conf.erb',
+         :name => 'openldap_stuff.conf',
+         :path => resource_path,
          :local => false,
          :cookbook => nil,
          :variables => {},
@@ -32,7 +46,10 @@ describe Chef::Provider::Template::Content do
          :helper_modules => [])
   end
 
-  let(:rendered_file_location) { Dir.tmpdir + '/openldap_stuff.conf' }
+  let(:rendered_file_locations) {
+    [Dir.tmpdir + '/openldap_stuff.conf',
+     enclosing_directory + '/openldap_stuff.conf']
+  }
 
   let(:run_context) do
     cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
@@ -50,7 +67,9 @@ describe Chef::Provider::Template::Content do
   end
 
   after do
-    FileUtils.rm(rendered_file_location) if ::File.exist?(rendered_file_location)
+    rendered_file_locations.each do |file|
+      FileUtils.rm(file) if ::File.exist?(file)
+    end
   end
 
   it "finds the template file in the cookbook cache if it isn't local" do
@@ -70,9 +89,81 @@ describe Chef::Provider::Template::Content do
     expect(content.template_location).to eq(CHEF_SPEC_DATA + '/cookbooks/openldap/templates/default/test.erb')
   end
 
+  it "returns a tempfile in the tempdir when :file_staging_uses_destdir is not set" do
+    Chef::Config[:file_staging_uses_destdir] = false
+    expect(content.tempfile.path.start_with?(Dir::tmpdir)).to be true
+    expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be false
+  end
+
+  it "returns a tempfile in the destdir when :file_staging_uses_destdir is set" do
+    Chef::Config[:file_staging_uses_destdir] = true
+    expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be true
+  end
+
+  context "when creating a tempfile in destdir fails" do
+    let(:enclosing_directory) {
+      canonicalize_path("/nonexisting/path")
+    }
+
+    it "returns a tempfile in the tempdir when :file_deployment_uses_destdir is set to :auto" do
+      Chef::Config[:file_staging_uses_destdir] = :auto
+      expect(content.tempfile.path.start_with?(Dir::tmpdir)).to be true
+      expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be false
+    end
+
+    it "fails when :file_desployment_uses_destdir is set" do
+      Chef::Config[:file_staging_uses_destdir] = true
+      expect{content.tempfile}.to raise_error(Chef::Exceptions::FileContentStagingError)
+    end
+
+    it "returns a tempfile in the tempdir when :file_desployment_uses_destdir is not set" do
+      expect(content.tempfile.path.start_with?(Dir::tmpdir)).to be true
+      expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be false
+    end
+  end
+
   it "creates the template with the rendered content" do
     run_context.node.normal[:slappiness] = "a warm gun"
     expect(IO.read(content.tempfile.path)).to eq("slappiness is a warm gun")
   end
 
+  describe "when using location helpers" do
+    let(:new_resource) do
+      double("Chef::Resource::Template (new)",
+             :cookbook_name => 'openldap',
+             :recipe_name => 'default',
+             :source_line => CHEF_SPEC_DATA + "/cookbooks/openldap/recipes/default.rb:2:in `from_file'",
+             :source_line_file => CHEF_SPEC_DATA + "/cookbooks/openldap/recipes/default.rb",
+             :source_line_number => "2",
+             :source => 'helpers.erb',
+             :name => 'helpers.erb',
+             :path => CHEF_SPEC_DATA + '/cookbooks/openldap/templates/default/helpers.erb',
+             :local => false,
+             :cookbook => nil,
+             :variables => {},
+             :inline_helper_blocks => {},
+             :inline_helper_modules => [],
+             :helper_modules => [])
+    end
+
+    it "creates the template with the rendered content" do
+      expect(IO.read(content.tempfile.path)).to eql <<EOF
+openldap
+default
+#{CHEF_SPEC_DATA}/cookbooks/openldap/recipes/default.rb:2:in `from_file'
+#{CHEF_SPEC_DATA}/cookbooks/openldap/recipes/default.rb
+2
+helpers.erb
+#{CHEF_SPEC_DATA}/cookbooks/openldap/templates/default/helpers.erb
+openldap
+default
+#{CHEF_SPEC_DATA}/cookbooks/openldap/recipes/default.rb:2:in `from_file'
+#{CHEF_SPEC_DATA}/cookbooks/openldap/recipes/default.rb
+2
+helpers.erb
+#{CHEF_SPEC_DATA}/cookbooks/openldap/templates/default/helpers.erb
+EOF
+    end
+
+  end
 end
diff --git a/spec/unit/provider/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb
index 5ea037d..a9407a4 100644
--- a/spec/unit/provider/user/dscl_spec.rb
+++ b/spec/unit/provider/user/dscl_spec.rb
@@ -24,7 +24,7 @@ require 'mixlib/shellout'
 
 describe Chef::Provider::User::Dscl do
   before do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
   end
   let(:node) {
     node = Chef::Node.new
@@ -760,6 +760,13 @@ ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30")
         provider.dscl_create_comment
       end
 
+      it "sets the comment field to username" do
+        new_resource.comment nil
+        expect(provider).to receive(:run_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
+        provider.dscl_create_comment
+        expect(new_resource.comment).to eq("#mockssuck")
+      end
+
       it "should run run_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
         expect(provider).to receive(:run_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true)
         provider.dscl_set_gid
@@ -789,6 +796,13 @@ ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30")
         expect { provider.dscl_set_gid }.to raise_error(Chef::Exceptions::GroupIDNotFound)
       end
     end
+
+    it "should set group ID to 20 if it's not specified" do
+      new_resource.gid nil
+      expect(provider).to receive(:run_dscl).with("create /Users/toor PrimaryGroupID '20'").ordered.and_return(true)
+      provider.dscl_set_gid
+      expect(new_resource.gid).to eq(20)
+    end
   end
 
   describe "when the user exists and chef is managing it" do
diff --git a/spec/unit/provider/user/solaris_spec.rb b/spec/unit/provider/user/solaris_spec.rb
index ef62fd1..a3c17a9 100644
--- a/spec/unit/provider/user/solaris_spec.rb
+++ b/spec/unit/provider/user/solaris_spec.rb
@@ -1,7 +1,9 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Dave Eddy (<dave at daveeddy.com>)
 # Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2015, Dave Eddy
 #
 # License:: Apache License, Version 2.0
 #
@@ -18,6 +20,9 @@
 # limitations under the License.
 #
 
+ShellCmdResult = Struct.new(:stdout, :stderr, :exitstatus)
+
+require 'mixlib/shellout'
 require 'spec_helper'
 
 describe Chef::Provider::User::Solaris do
@@ -31,15 +36,6 @@ describe Chef::Provider::User::Solaris do
     p
   end
 
-  supported_useradd_options = {
-    'comment' => "-c",
-    'gid' => "-g",
-    'uid' => "-u",
-    'shell' => "-s"
-  }
-
-  include_examples "a useradd-based user provider", supported_useradd_options
-
   describe "when we want to set a password" do
     before(:each) do
       @node = Chef::Node.new
@@ -77,4 +73,65 @@ describe Chef::Provider::User::Solaris do
     end
   end
 
+  describe 'when managing user locked status' do
+    before(:each) do
+      @node = Chef::Node.new
+      @events = Chef::EventDispatch::Dispatcher.new
+      @run_context = Chef::RunContext.new(@node, {}, @events)
+
+      @new_resource = Chef::Resource::User.new('dave')
+      @current_resource = @new_resource.dup
+
+      @provider = Chef::Provider::User::Solaris.new(@new_resource, @run_context)
+      @provider.current_resource = @current_resource
+    end
+    describe 'when determining if the user is locked' do
+
+      # locked shadow lines
+      [
+        'dave:LK:::::::',
+        'dave:*LK*:::::::',
+        'dave:*LK*foobar:::::::',
+        'dave:*LK*bahamas10:::::::',
+        'dave:*LK*L....:::::::',
+      ].each do |shadow|
+        it "should return true if user is locked with #{shadow}" do
+          shell_return = ShellCmdResult.new(shadow + "\n", '', 0)
+          expect(provider).to receive(:shell_out!).with('getent', 'shadow', @new_resource.username).and_return(shell_return)
+          expect(provider.check_lock).to eql(true)
+        end
+      end
+
+      # unlocked shadow lines
+      [
+        'dave:NP:::::::',
+        'dave:*NP*:::::::',
+        'dave:foobar:::::::',
+        'dave:bahamas10:::::::',
+        'dave:L...:::::::',
+      ].each do |shadow|
+        it "should return false if user is unlocked with #{shadow}" do
+          shell_return = ShellCmdResult.new(shadow + "\n", '', 0)
+          expect(provider).to receive(:shell_out!).with('getent', 'shadow', @new_resource.username).and_return(shell_return)
+          expect(provider.check_lock).to eql(false)
+        end
+      end
+    end
+
+    describe 'when locking the user' do
+      it 'should run passwd -l with the new resources username' do
+        shell_return = ShellCmdResult.new('', '', 0)
+        expect(provider).to receive(:shell_out!).with('passwd', '-l', @new_resource.username).and_return(shell_return)
+        provider.lock_user
+      end
+    end
+
+    describe 'when unlocking the user' do
+      it 'should run passwd -u with the new resources username' do
+        shell_return = ShellCmdResult.new('', '', 0)
+        expect(provider).to receive(:shell_out!).with('passwd', '-u', @new_resource.username).and_return(shell_return)
+        provider.unlock_user
+      end
+    end
+  end
 end
diff --git a/spec/unit/provider/user/windows_spec.rb b/spec/unit/provider/user/windows_spec.rb
index e51e20a..7e08f97 100644
--- a/spec/unit/provider/user/windows_spec.rb
+++ b/spec/unit/provider/user/windows_spec.rb
@@ -107,8 +107,8 @@ describe Chef::Provider::User::Windows do
         expect(@provider.set_options[:home_dir]).to eq('/home/adam')
       end
 
-      it "marks the primary_group_id attribute to be updated" do
-        expect(@provider.set_options[:primary_group_id]).to eq(1000)
+      it "ignores the primary_group_id attribute" do
+        expect(@provider.set_options[:primary_group_id]).to eq(nil)
       end
 
       it "marks the user_id attribute to be updated" do
diff --git a/spec/unit/provider/user_spec.rb b/spec/unit/provider/user_spec.rb
index 3811686..bd24a6a 100644
--- a/spec/unit/provider/user_spec.rb
+++ b/spec/unit/provider/user_spec.rb
@@ -143,8 +143,8 @@ describe Chef::Provider::User do
         begin
           require 'rubygems'
           require 'shadow'
-        rescue LoadError => e
-          pending "ruby-shadow gem not installed for dynamic load test"
+        rescue LoadError
+          skip "ruby-shadow gem not installed for dynamic load test"
           true
         else
           false
@@ -161,7 +161,7 @@ describe Chef::Provider::User do
 
       unless shadow_lib_unavail?
         context "and we have the ruby-shadow gem" do
-          pending "and we are not root (rerun this again as root)", :requires_unprivileged_user => true
+          skip "and we are not root (rerun this again as root)", :requires_unprivileged_user => true
 
           context "and we are root", :requires_root => true do
             it "should pass assertions when ruby-shadow can be loaded" do
@@ -452,11 +452,20 @@ describe Chef::Provider::User do
 
     it "should raise an error if we can't translate the group name during resource assertions" do
       expect(Etc).to receive(:getgrnam).and_raise(ArgumentError)
+      @provider.action = :create
       @provider.define_resource_requirements
       @provider.convert_group_name
       expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::User)
     end
 
+    it "does not raise an error if we can't translate the group name during resource assertions if we are removing the user" do
+      expect(Etc).to receive(:getgrnam).and_raise(ArgumentError)
+      @provider.action = :remove
+      @provider.define_resource_requirements
+      @provider.convert_group_name
+      expect { @provider.process_resource_requirements }.not_to raise_error
+    end
+
     it "should set the new resources gid to the integerized version if available" do
       expect(Etc).to receive(:getgrnam).with("999").and_return(@group)
       @provider.convert_group_name
diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb
index bdf6d06..c125618 100644
--- a/spec/unit/provider_resolver_spec.rb
+++ b/spec/unit/provider_resolver_spec.rb
@@ -19,342 +19,423 @@
 require 'spec_helper'
 require 'chef/mixin/convert_to_class_name'
 require 'chef/provider_resolver'
+require 'chef/platform/service_helpers'
+require 'support/shared/integration/integration_helper'
+require 'tmpdir'
+require 'fileutils'
 
 include Chef::Mixin::ConvertToClassName
 
-describe Chef::ProviderResolver do
-
-  let(:node) do
-    node = Chef::Node.new
-    allow(node).to receive(:[]).with(:os).and_return(os)
-    allow(node).to receive(:[]).with(:platform_family).and_return(platform_family)
-    allow(node).to receive(:[]).with(:platform).and_return(platform)
-    allow(node).to receive(:[]).with(:platform_version).and_return(platform_version)
-    allow(node).to receive(:is_a?).and_return(Chef::Node)
-    node
-  end
-
-  let(:provider_resolver) { Chef::ProviderResolver.new(node, resource, action) }
-
-  let(:action) { :start }
-
-  let(:resolved_provider) { provider_resolver.resolve }
+# Open up Provider so we can write things down easier in here
+#module Chef::Provider
 
-  let(:provider) { nil }
-
-  let(:resource_name) { :service }
-
-  let(:resource) { double(Chef::Resource, provider: provider, resource_name: resource_name) }
-
-  before do
-    allow(resource).to receive(:is_a?).with(Chef::Resource).and_return(true)
-  end
+describe Chef::ProviderResolver do
+  include IntegrationSupport
 
-  describe "resolving service resource" do
-    def stub_service_providers(*services)
-      services ||= []
-      allow(Chef::Platform::ServiceHelpers).to receive(:service_resource_providers)
-        .and_return(services)
+  # Root the filesystem under a temp directory so Chef.path_to will point at it
+  when_the_repository "is empty" do
+    before do
+      allow(Chef).to receive(:path_to) { |path| File.join(path_to(""), path) }
     end
 
-    def stub_service_configs(*configs)
-      configs ||= []
-      allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-        .and_return(configs)
+    let(:resource_name) { :service }
+    let(:provider) { nil }
+    let(:action) { :start }
+
+    let(:node) do
+      node = Chef::Node.new
+      node.automatic[:os] = os
+      node.automatic[:platform_family] = platform_family
+      node.automatic[:platform] = platform
+      node.automatic[:platform_version] = platform_version
+      node.automatic[:kernel] = { machine: 'i386' }
+      node
     end
-
-    before do
-      expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup)
-      allow(resource).to receive(:service_name).and_return("ntp")
+    let(:run_context) { Chef::RunContext.new(node, nil, nil) }
+
+    let(:provider_resolver) { Chef::ProviderResolver.new(node, resource, action) }
+    let(:resolved_provider) do
+      begin
+        resource ? resource.provider_for_action(action).class : nil
+      rescue Chef::Exceptions::ProviderNotFound
+        nil
+      end
     end
 
-    shared_examples_for "an ubuntu platform with upstart, update-rc.d and systemd" do
-      before do
-        stub_service_providers(:debian, :invokercd, :upstart, :systemd)
+    let(:service_name) { "test" }
+    let(:resource) do
+      resource_class = Chef::ResourceResolver.resolve(resource_name, node: node)
+      if resource_class
+        resource = resource_class.new(service_name, run_context)
+        resource.provider = provider if provider
       end
+      resource
+    end
 
-      it "when only the SysV init script exists, it returns a Service::Debian provider" do
-        allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-          .and_return( [ :initd, :systemd ] )
-        expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
+    def self.on_platform(platform, *tags,
+      platform_version: '11.0.1',
+      platform_family: nil,
+      os: nil,
+      &block)
+      Array(platform).each do |platform|
+        Array(platform_version).each do |platform_version|
+          on_one_platform(platform, platform_version, platform_family || platform, os || platform_family || platform, *tags, &block)
+        end
       end
+    end
 
-      it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do
-        allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-          .and_return( [ :initd, :upstart, :systemd ] )
-        expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
-      end
+    def self.on_one_platform(platform, platform_version, platform_family, os, *tags, &block)
+      describe "on #{platform} #{platform_version}, platform_family: #{platform_family}, os: #{os}", *tags do
+        let(:os)               { os }
+        let(:platform)         { platform }
+        let(:platform_family)  { platform_family }
+        let(:platform_version) { platform_version }
 
-      it "when only the Upstart script exists, it returns a Service::Upstart provider" do
-        allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-          .and_return( [ :upstart, :systemd ] )
-        expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
-      end
+        define_singleton_method(:os) { os }
+        define_singleton_method(:platform) { platform }
+        define_singleton_method(:platform_family) { platform_family }
+        define_singleton_method(:platform_version) { platform_version }
 
-      it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do
-        allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-          .and_return( [ :systemd ] )
-        expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
-      end
-      it "when only the SysV init script exists, it returns a Service::Debian provider" do
-        allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-          .and_return( [ :initd ] )
-        expect(resolved_provider).to eql(Chef::Provider::Service::Debian)
+        instance_eval(&block)
       end
+    end
 
-      it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do
-        allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-          .and_return( [ :initd, :upstart ] )
-        expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
-      end
+    def self.expect_providers(**providers)
+      providers.each do |name, expected|
+        describe name.to_s do
+          let(:resource_name) { name }
+
+          tags = []
+          expected_provider = nil
+          expected_resource = nil
+          Array(expected).each do |p|
+            if p.is_a?(Class) && p <= Chef::Provider
+              expected_provider = p
+            elsif p.is_a?(Class) && p <= Chef::Resource
+              expected_resource = p
+            else
+              tags << p
+            end
+          end
 
-      it "when only the Upstart script exists, it returns a Service::Upstart provider" do
-        allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-          .and_return( [ :upstart ] )
-        expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
+          if expected_resource && expected_provider
+            it "'#{name}' resolves to resource #{expected_resource} and provider #{expected_provider}", *tags do
+              expect(resource.class).to eql(expected_resource)
+              provider = double(expected_provider, class: expected_provider)
+              expect(provider).to receive(:action=).with(action)
+              expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider)
+              expect(resolved_provider).to eql(expected_provider)
+            end
+          elsif expected_provider
+            it "'#{name}' resolves to provider #{expected_provider}", *tags do
+              provider = double(expected_provider)
+              expect(provider).to receive(:action=).with(action)
+              expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider)
+              expect(resolved_provider).to eql(expected_provider)
+            end
+          else
+            it "'#{name}' fails to resolve (since #{name.inspect} is unsupported on #{platform} #{platform_version})", *tags do
+              expect(resolved_provider).to be_nil
+            end
+          end
+        end
       end
+    end
 
-      it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do
-        allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-          .and_return( [ ] )
-        expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
+    describe "resolving service resource" do
+      def stub_service_providers(*services)
+        services.each do |service|
+          case service
+          when :debian
+            file 'usr/sbin/update-rc.d', ''
+          when :invokercd
+            file 'usr/sbin/invoke-rc.d', ''
+          when :insserv
+            file 'sbin/insserv', ''
+          when :upstart
+            file 'sbin/initctl', ''
+          when :redhat
+            file 'sbin/chkconfig', ''
+          when :systemd
+            file 'proc/1/comm', "systemd\n"
+          else
+            raise ArgumentError, service
+          end
+        end
       end
-    end
 
-    shared_examples_for "an ubuntu platform with upstart and update-rc.d" do
-      before do
-        stub_service_providers(:debian, :invokercd, :upstart)
+      def stub_service_configs(*configs)
+        configs.each do |config|
+          case config
+          when :initd
+            file "etc/init.d/#{service_name}", ""
+          when :upstart
+            file "etc/init/#{service_name}.conf", ""
+          when :xinetd
+            file "etc/xinetd.d/#{service_name}", ""
+          when :etc_rcd
+            file "etc/rc.d/#{service_name}", ""
+          when :usr_local_etc_rcd
+            file "usr/local/etc/rc.d/#{service_name}", ""
+          when :systemd
+            file 'proc/1/comm', "systemd\n"
+            file "etc/systemd/system/#{service_name}.service", ""
+          else
+            raise ArgumentError, config
+          end
+        end
       end
 
-      # needs to be handled by the highest priority init.d handler
-      context "when only the SysV init script exists" do
+      shared_examples_for "an ubuntu platform with upstart, update-rc.d and systemd" do
         before do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ :initd ] )
+          stub_service_providers(:debian, :invokercd, :upstart, :systemd)
         end
 
-        it "enables init, invokercd, debian and upstart providers" do
-          expect(provider_resolver.enabled_handlers).to include(
-            Chef::Provider::Service::Debian,
-            Chef::Provider::Service::Init,
-            Chef::Provider::Service::Invokercd,
-            Chef::Provider::Service::Upstart,
-          )
+        it "when only the SysV init script exists, it returns a Service::Debian provider" do
+          stub_service_configs(:initd, :systemd)
+          expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
         end
 
-        it "supports all the enabled handlers except for upstart" do
-          expect(provider_resolver.supported_handlers).to include(
-            Chef::Provider::Service::Debian,
-            Chef::Provider::Service::Init,
-            Chef::Provider::Service::Invokercd,
-          )
-          expect(provider_resolver.supported_handlers).to_not include(
-            Chef::Provider::Service::Upstart,
-          )
+        it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do
+          stub_service_configs(:initd, :upstart, :systemd)
+          expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
         end
 
-        it "returns a Service::Debian provider" do
-          expect(resolved_provider).to eql(Chef::Provider::Service::Debian)
+        it "when only the Upstart script exists, it returns a Service::Upstart provider" do
+          stub_service_configs(:upstart, :systemd)
+          expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
         end
-      end
 
-      # on ubuntu this must be handled by upstart, the init script will exit 1 and fail
-      context "when both SysV and Upstart scripts exist" do
-        before do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ :initd, :upstart ] )
+        it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do
+          stub_service_configs(:systemd)
+          expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
         end
 
-        it "enables init, invokercd, debian and upstart providers" do
-          expect(provider_resolver.enabled_handlers).to include(
-            Chef::Provider::Service::Debian,
-            Chef::Provider::Service::Init,
-            Chef::Provider::Service::Invokercd,
-            Chef::Provider::Service::Upstart,
-          )
+        it "when only the SysV init script exists, it returns a Service::Debian provider" do
+          stub_service_configs(:initd)
+          expect(resolved_provider).to eql(Chef::Provider::Service::Debian)
         end
 
-        it "supports all the enabled handlers" do
-          expect(provider_resolver.supported_handlers).to include(
-            Chef::Provider::Service::Debian,
-            Chef::Provider::Service::Init,
-            Chef::Provider::Service::Invokercd,
-            Chef::Provider::Service::Upstart,
-          )
+        it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do
+          stub_service_configs(:initd, :upstart)
+          expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
         end
 
-        it "returns a Service::Upstart provider" do
+        it "when only the Upstart script exists, it returns a Service::Upstart provider" do
+          stub_service_configs(:upstart)
           expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
         end
+
+        it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do
+          stub_service_configs
+          expect(resolved_provider).to eql(Chef::Provider::Service::Systemd)
+        end
       end
 
-      # this case is a pure-upstart script which is easy
-      context "when only the Upstart script exists" do
+      shared_examples_for "an ubuntu platform with upstart and update-rc.d" do
         before do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ :upstart ] )
+          stub_service_providers(:debian, :invokercd, :upstart)
         end
 
-        it "enables init, invokercd, debian and upstart providers" do
-          expect(provider_resolver.enabled_handlers).to include(
-            Chef::Provider::Service::Debian,
-            Chef::Provider::Service::Init,
-            Chef::Provider::Service::Invokercd,
-            Chef::Provider::Service::Upstart,
-          )
-        end
+        # needs to be handled by the highest priority init.d handler
+        context "when only the SysV init script exists" do
+          before do
+            stub_service_configs(:initd)
+          end
 
-        it "supports only the upstart handler" do
-          expect(provider_resolver.supported_handlers).to include(
-            Chef::Provider::Service::Upstart,
-          )
-          expect(provider_resolver.supported_handlers).to_not include(
-            Chef::Provider::Service::Debian,
-            Chef::Provider::Service::Init,
-            Chef::Provider::Service::Invokercd,
-          )
-        end
+          it "enables init, invokercd, debian and upstart providers" do
+            expect(provider_resolver.enabled_handlers).to include(
+              Chef::Provider::Service::Debian,
+              Chef::Provider::Service::Init,
+              Chef::Provider::Service::Invokercd,
+              Chef::Provider::Service::Upstart,
+            )
+          end
 
-        it "returns a Service::Upstart provider" do
-          expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
-        end
-      end
+          it "supports all the enabled handlers except for upstart" do
+            expect(provider_resolver.supported_handlers).to include(
+              Chef::Provider::Service::Debian,
+              Chef::Provider::Service::Init,
+              Chef::Provider::Service::Invokercd,
+            )
+            expect(provider_resolver.supported_handlers).to_not include(
+              Chef::Provider::Service::Upstart,
+            )
+          end
 
-      # this case is important to get correct for why-run when no config is setup
-      context "when both do not exist" do
-        before do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ ] )
+          it "returns a Service::Debian provider" do
+            expect(resolved_provider).to eql(Chef::Provider::Service::Debian)
+          end
         end
 
-        it "enables init, invokercd, debian and upstart providers" do
-          expect(provider_resolver.enabled_handlers).to include(
-            Chef::Provider::Service::Debian,
-            Chef::Provider::Service::Init,
-            Chef::Provider::Service::Invokercd,
-            Chef::Provider::Service::Upstart,
-          )
-        end
+        # on ubuntu this must be handled by upstart, the init script will exit 1 and fail
+        context "when both SysV and Upstart scripts exist" do
+          before do
+            stub_service_configs(:initd, :upstart)
+          end
 
-        it "no providers claim to support the resource" do
-          expect(provider_resolver.supported_handlers).to_not include(
-            Chef::Provider::Service::Upstart,
-            Chef::Provider::Service::Debian,
-            Chef::Provider::Service::Init,
-            Chef::Provider::Service::Invokercd,
-          )
-        end
+          it "enables init, invokercd, debian and upstart providers" do
+            expect(provider_resolver.enabled_handlers).to include(
+              Chef::Provider::Service::Debian,
+              Chef::Provider::Service::Init,
+              Chef::Provider::Service::Invokercd,
+              Chef::Provider::Service::Upstart,
+            )
+          end
 
-        it "returns a Debian Provider" do
-          expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
-        end
-      end
-    end
+          it "supports all the enabled handlers" do
+            expect(provider_resolver.supported_handlers).to include(
+              Chef::Provider::Service::Debian,
+              Chef::Provider::Service::Init,
+              Chef::Provider::Service::Invokercd,
+              Chef::Provider::Service::Upstart,
+            )
+          end
 
-    shared_examples_for "a debian platform using the insserv provider" do
-      context "with a default install" do
-        before do
-          stub_service_providers(:debian, :invokercd, :insserv)
+          it "returns a Service::Upstart provider" do
+            expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
+          end
         end
 
-        it "uses the Service::Insserv Provider to manage sysv init scripts" do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ :initd ] )
-          expect(resolved_provider).to eql(Chef::Provider::Service::Insserv)
-        end
+        # this case is a pure-upstart script which is easy
+        context "when only the Upstart script exists" do
+          before do
+            stub_service_configs(:upstart)
+          end
 
-        it "uses the Service::Insserv Provider when there is no config" do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ ] )
-          expect(resolved_provider).to eql(Chef::Provider::Service::Insserv)
-        end
-      end
+          it "enables init, invokercd, debian and upstart providers" do
+            expect(provider_resolver.enabled_handlers).to include(
+              Chef::Provider::Service::Debian,
+              Chef::Provider::Service::Init,
+              Chef::Provider::Service::Invokercd,
+              Chef::Provider::Service::Upstart,
+            )
+          end
 
-      context "when the user has installed upstart" do
-        before do
-          stub_service_providers(:debian, :invokercd, :insserv, :upstart)
-        end
+          it "supports only the upstart handler" do
+            expect(provider_resolver.supported_handlers).to include(
+              Chef::Provider::Service::Upstart,
+            )
+            expect(provider_resolver.supported_handlers).to_not include(
+              Chef::Provider::Service::Debian,
+              Chef::Provider::Service::Init,
+              Chef::Provider::Service::Invokercd,
+            )
+          end
 
-        it "when only the SysV init script exists, it returns an Insserv  provider" do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ :initd ] )
-          expect(resolved_provider).to eql(Chef::Provider::Service::Insserv)
+          it "returns a Service::Upstart provider" do
+            expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
+          end
         end
 
-        it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ :initd, :upstart ] )
-          expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
-        end
+        # this case is important to get correct for why-run when no config is setup
+        context "when both do not exist" do
+          before do
+            stub_service_configs
+          end
 
-        it "when only the Upstart script exists, it returns a Service::Upstart provider" do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ :upstart ] )
-          expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
-        end
+          it "enables init, invokercd, debian and upstart providers" do
+            expect(provider_resolver.enabled_handlers).to include(
+              Chef::Provider::Service::Debian,
+              Chef::Provider::Service::Init,
+              Chef::Provider::Service::Invokercd,
+              Chef::Provider::Service::Upstart,
+            )
+          end
 
-        it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do
-          allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp")
-            .and_return( [ ] )
-          expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
+          it "no providers claim to support the resource" do
+            expect(provider_resolver.supported_handlers).to_not include(
+              Chef::Provider::Service::Upstart,
+              Chef::Provider::Service::Debian,
+              Chef::Provider::Service::Init,
+              Chef::Provider::Service::Invokercd,
+            )
+          end
+
+          it "returns a Debian Provider" do
+            expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
+          end
         end
       end
-    end
 
-    describe "on Ubuntu 14.10" do
-      let(:os) { "linux" }
-      let(:platform) { "ubuntu" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "14.04" }
+      shared_examples_for "a debian platform using the insserv provider" do
+        context "with a default install" do
+          before do
+            stub_service_providers(:debian, :invokercd, :insserv)
+          end
 
-      it_behaves_like "an ubuntu platform with upstart, update-rc.d and systemd"
-    end
+          it "uses the Service::Insserv Provider to manage sysv init scripts" do
+            stub_service_configs(:initd)
+            expect(resolved_provider).to eql(Chef::Provider::Service::Insserv)
+          end
 
-    describe "on Ubuntu 14.04" do
-      let(:os) { "linux" }
-      let(:platform) { "ubuntu" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "14.04" }
+          it "uses the Service::Insserv Provider when there is no config" do
+            stub_service_configs
+            expect(resolved_provider).to eql(Chef::Provider::Service::Insserv)
+          end
+        end
 
-      it_behaves_like "an ubuntu platform with upstart and update-rc.d"
-    end
+        context "when the user has installed upstart" do
+          before do
+            stub_service_providers(:debian, :invokercd, :insserv, :upstart)
+          end
 
-    describe "on Ubuntu 10.04" do
-      let(:os) { "linux" }
-      let(:platform) { "ubuntu" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "10.04" }
+          it "when only the SysV init script exists, it returns an Insserv  provider" do
+            stub_service_configs(:initd)
+            expect(resolved_provider).to eql(Chef::Provider::Service::Insserv)
+          end
 
-      it_behaves_like "an ubuntu platform with upstart and update-rc.d"
-    end
+          it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do
+            stub_service_configs(:initd, :upstart)
+            expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
+          end
 
-    # old debian uses the Debian provider (does not have insserv or upstart, or update-rc.d???)
-    describe "on Debian 4.0" do
-      let(:os) { "linux" }
-      let(:platform) { "debian" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "4.0" }
+          it "when only the Upstart script exists, it returns a Service::Upstart provider" do
+            stub_service_configs(:upstart)
+            expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
+          end
 
-      #it_behaves_like "a debian platform using the debian provider"
-    end
+          it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do
+            stub_service_configs
+            expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
+          end
+        end
+      end
 
-    # Debian replaced the debian provider with insserv in the FIXME:VERSION distro
-    describe "on Debian 7.0" do
-      let(:os) { "linux" }
-      let(:platform) { "debian" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "7.0" }
+      on_platform "ubuntu", platform_version: "15.10", platform_family: "debian", os: "linux" do
+        it_behaves_like "an ubuntu platform with upstart, update-rc.d and systemd"
 
-      it_behaves_like "a debian platform using the insserv provider"
-    end
+        it "when the unit-files are missing and system-ctl list-unit-files returns an error" do
+          stub_service_providers(:debian, :invokercd, :upstart, :systemd)
+          stub_service_configs(:initd, :upstart)
+          mock_shellout_command("/bin/systemctl list-unit-files", exitstatus: 1)
+          expect(resolved_provider).to eql(Chef::Provider::Service::Upstart)
+        end
+      end
+
+      on_platform "ubuntu", platform_version: "14.10", platform_family: "debian", os: "linux" do
+        it_behaves_like "an ubuntu platform with upstart, update-rc.d and systemd"
+      end
 
-    %w{solaris2 openindiana opensolaris nexentacore omnios smartos}.each do |platform|
-      describe "on #{platform}" do
-        let(:os) { "solaris2" }
-        let(:platform) { platform }
-        let(:platform_family) { platform }
-        let(:platform_version) { "5.11" }
+      on_platform "ubuntu", platform_version: "14.04", platform_family: "debian", os: "linux" do
+        it_behaves_like "an ubuntu platform with upstart and update-rc.d"
+      end
+
+      on_platform "ubuntu", platform_version: "10.04", platform_family: "debian", os: "linux" do
+        it_behaves_like "an ubuntu platform with upstart and update-rc.d"
+      end
+
+      # old debian uses the Debian provider (does not have insserv or upstart, or update-rc.d???)
+      on_platform "debian", platform_version: "4.0", os: "linux" do
+        #it_behaves_like "a debian platform using the debian provider"
+      end
 
+      # Debian replaced the debian provider with insserv in the FIXME:VERSION distro
+      on_platform "debian", platform_version: "7.0", os: "linux" do
+        it_behaves_like "a debian platform using the insserv provider"
+      end
+
+      on_platform %w{solaris2 openindiana opensolaris nexentacore omnios smartos}, os: "solaris2", platform_version: "5.11" do
         it "returns a Solaris provider" do
           stub_service_providers
           stub_service_configs
@@ -364,19 +445,12 @@ describe Chef::ProviderResolver do
         it "always returns a Solaris provider" do
           # no matter what we stub on the next two lines we should get a Solaris provider
           stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
-          stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd)
+          stub_service_configs(:initd, :upstart, :xinetd, :usr_local_etc_rcd, :systemd)
           expect(resolved_provider).to eql(Chef::Provider::Service::Solaris)
         end
       end
-    end
-
-    %w{mswin mingw32 windows}.each do |platform|
-      describe "on #{platform}" do
-        let(:os) { "windows" }
-        let(:platform) { platform }
-        let(:platform_family) { "windows" }
-        let(:platform_version) { "5.11" }
 
+      on_platform %w{mswin mingw32 windows}, platform_family: "windows", platform_version: "5.11" do
         it "returns a Windows provider" do
           stub_service_providers
           stub_service_configs
@@ -386,19 +460,12 @@ describe Chef::ProviderResolver do
         it "always returns a Windows provider" do
           # no matter what we stub on the next two lines we should get a Windows provider
           stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
-          stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd)
+          stub_service_configs(:initd, :upstart, :xinetd, :usr_local_etc_rcd, :systemd)
           expect(resolved_provider).to eql(Chef::Provider::Service::Windows)
         end
       end
-    end
-
-    %w{mac_os_x mac_os_x_server}.each do |platform|
-      describe "on #{platform}" do
-        let(:os) { "darwin" }
-        let(:platform) { platform }
-        let(:platform_family) { "mac_os_x" }
-        let(:platform_version) { "10.9.2" }
 
+      on_platform %w{mac_os_x mac_os_x_server}, os: "darwin", platform_family: "mac_os_x", platform_version: "10.9.2" do
         it "returns a Macosx provider" do
           stub_service_providers
           stub_service_configs
@@ -408,19 +475,12 @@ describe Chef::ProviderResolver do
         it "always returns a Macosx provider" do
           # no matter what we stub on the next two lines we should get a Macosx provider
           stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
-          stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd)
+          stub_service_configs(:initd, :upstart, :xinetd, :usr_local_etc_rcd, :systemd)
           expect(resolved_provider).to eql(Chef::Provider::Service::Macosx)
         end
       end
-    end
-
-    %w{freebsd netbsd}.each do |platform|
-      describe "on #{platform}" do
-        let(:os) { platform }
-        let(:platform) { platform }
-        let(:platform_family) { platform }
-        let(:platform_version) { "10.0-RELEASE" }
 
+      on_platform %w(freebsd netbsd), platform_version: '3.1.4' do
         it "returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
           stub_service_providers
           stub_service_configs(:usr_local_etc_rcd)
@@ -453,237 +513,380 @@ describe Chef::ProviderResolver do
           expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
         end
       end
-    end
 
-  end
-
-  describe "for the package provider" do
-    let(:resource_name) { :package }
-
-    before do
-      expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup)
     end
 
-    %w{mac_os_x mac_os_x_server}.each do |platform|
-      describe "on #{platform}" do
-        let(:os) { "darwin" }
-        let(:platform) { platform }
-        let(:platform_family) { "mac_os_x" }
-        let(:platform_version) { "10.9.2" }
-
-
-        it "returns a Chef::Provider::Package::Homebrew provider" do
-          expect(resolved_provider).to eql(Chef::Provider::Package::Homebrew)
-        end
-      end
-    end
-  end
-
-  provider_mapping = {
-    "mac_os_x" => {
-      :package => Chef::Provider::Package::Homebrew,
-      :user => Chef::Provider::User::Dscl,
-      :group => Chef::Provider::Group::Dscl,
-    },
-    "mac_os_x_server" => {
-      :package => Chef::Provider::Package::Homebrew,
-      :user => Chef::Provider::User::Dscl,
-      :group => Chef::Provider::Group::Dscl,
-    },
-    "mswin" => {
-      :env =>  Chef::Provider::Env::Windows,
-      :user => Chef::Provider::User::Windows,
-      :group => Chef::Provider::Group::Windows,
-      :mount => Chef::Provider::Mount::Windows,
-      :batch => Chef::Provider::Batch,
-      :powershell_script => Chef::Provider::PowershellScript,
-    },
-    "mingw32" => {
-      :env =>  Chef::Provider::Env::Windows,
-      :user => Chef::Provider::User::Windows,
-      :group => Chef::Provider::Group::Windows,
-      :mount => Chef::Provider::Mount::Windows,
-      :batch => Chef::Provider::Batch,
-      :powershell_script => Chef::Provider::PowershellScript,
-    },
-    "windows" => {
-      :env =>  Chef::Provider::Env::Windows,
-      :user => Chef::Provider::User::Windows,
-      :group => Chef::Provider::Group::Windows,
-      :mount => Chef::Provider::Mount::Windows,
-      :batch => Chef::Provider::Batch,
-      :powershell_script => Chef::Provider::PowershellScript,
-    },
-    "aix" => {
-      :cron => Chef::Provider::Cron::Aix,
-    },
-    "netbsd"=> {
-      :group => Chef::Provider::Group::Groupmod,
-    },
-    "openbsd" => {
-      :group => Chef::Provider::Group::Usermod,
-      :package => Chef::Provider::Package::Openbsd,
-    },
-  }
-
-  def self.do_platform(platform_hash)
-    platform_hash.each do |resource, provider|
-      describe "for #{resource}" do
-        let(:resource_name) { resource }
-
-        it "resolves to a #{provider}" do
-          expect(resolved_provider).to eql(provider)
-        end
-      end
-    end
-  end
-
-  describe "individual platform mappings" do
-    let(:resource_name) { :user }
-
-    before do
-      expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup)
-    end
-
-    %w{mac_os_x mac_os_x_server}.each do |platform|
-      describe "on #{platform}" do
-        let(:os) { "darwin" }
-        let(:platform) { platform }
-        let(:platform_family) { "mac_os_x" }
-        let(:platform_version) { "10.9.2" }
-
-        do_platform(provider_mapping[platform])
-      end
-    end
-
-    %w{mswin mingw32 windows}.each do |platform|
-      describe "on #{platform}" do
-        let(:os) { "windows" }
-        let(:platform) { platform }
-        let(:platform_family) { "windows" }
-        let(:platform_version) { "10.9.2" }
-
-        do_platform(provider_mapping[platform])
-      end
-    end
-
-    describe "on AIX" do
-      let(:os) { "aix" }
-      let(:platform) { "aix" }
-      let(:platform_family) { "aix" }
-      let(:platform_version) { "6.2" }
-
-      do_platform(provider_mapping['aix'])
-    end
-
-    %w{netbsd openbsd}.each do |platform|
-      describe "on #{platform}" do
-        let(:os) { platform }
-        let(:platform) { platform }
-        let(:platform_family) { platform }
-        let(:platform_version) { "10.0-RELEASE" }
-
-        do_platform(provider_mapping[platform])
-      end
-    end
-  end
-
-  describe "resolving static providers" do
-    def resource_class(resource)
-      Chef::Resource.const_get(convert_to_class_name(resource.to_s))
-    end
-      static_mapping = {
-        apt_package:  Chef::Provider::Package::Apt,
-        bash: Chef::Provider::Script,
-        bff_package: Chef::Provider::Package::Aix,
-        breakpoint:  Chef::Provider::Breakpoint,
-        chef_gem: Chef::Provider::Package::Rubygems,
-        cookbook_file:  Chef::Provider::CookbookFile,
-        csh:  Chef::Provider::Script,
-        deploy:   Chef::Provider::Deploy::Timestamped,
-        deploy_revision:  Chef::Provider::Deploy::Revision,
-        directory:  Chef::Provider::Directory,
-        dpkg_package: Chef::Provider::Package::Dpkg,
-        dsc_script: Chef::Provider::DscScript,
-        easy_install_package:  Chef::Provider::Package::EasyInstall,
-        erl_call: Chef::Provider::ErlCall,
-        execute:  Chef::Provider::Execute,
-        file: Chef::Provider::File,
-        gem_package: Chef::Provider::Package::Rubygems,
-        git:  Chef::Provider::Git,
-        homebrew_package: Chef::Provider::Package::Homebrew,
-        http_request: Chef::Provider::HttpRequest,
-        ips_package: Chef::Provider::Package::Ips,
-        link:  Chef::Provider::Link,
-        log:  Chef::Provider::Log::ChefLog,
-        macports_package:  Chef::Provider::Package::Macports,
-        mdadm:  Chef::Provider::Mdadm,
-        pacman_package: Chef::Provider::Package::Pacman,
-        paludis_package: Chef::Provider::Package::Paludis,
-        perl: Chef::Provider::Script,
-        portage_package:  Chef::Provider::Package::Portage,
-        python: Chef::Provider::Script,
-        remote_directory: Chef::Provider::RemoteDirectory,
-        route:  Chef::Provider::Route,
-        rpm_package:  Chef::Provider::Package::Rpm,
-        ruby:  Chef::Provider::Script,
-        ruby_block:   Chef::Provider::RubyBlock,
-        script:   Chef::Provider::Script,
-        smartos_package:  Chef::Provider::Package::SmartOS,
-        solaris_package:  Chef::Provider::Package::Solaris,
-        subversion:   Chef::Provider::Subversion,
-        template:   Chef::Provider::Template,
-        timestamped_deploy:  Chef::Provider::Deploy::Timestamped,
-        whyrun_safe_ruby_block:  Chef::Provider::WhyrunSafeRubyBlock,
-        windows_package:  Chef::Provider::Package::Windows,
-        windows_service:  Chef::Provider::Service::Windows,
-        yum_package:  Chef::Provider::Package::Yum,
+    PROVIDERS =
+    {
+      bash:                   [ Chef::Resource::Bash, Chef::Provider::Script ],
+      breakpoint:             [ Chef::Resource::Breakpoint, Chef::Provider::Breakpoint ],
+      chef_gem:               [ Chef::Resource::ChefGem, Chef::Provider::Package::Rubygems ],
+      cookbook_file:          [ Chef::Resource::CookbookFile, Chef::Provider::CookbookFile ],
+      csh:                    [ Chef::Resource::Csh, Chef::Provider::Script ],
+      deploy:                 [ Chef::Resource::Deploy, Chef::Provider::Deploy::Timestamped ],
+      deploy_revision:        [ Chef::Resource::DeployRevision, Chef::Provider::Deploy::Revision ],
+      directory:              [ Chef::Resource::Directory, Chef::Provider::Directory ],
+      easy_install_package:   [ Chef::Resource::EasyInstallPackage, Chef::Provider::Package::EasyInstall ],
+      erl_call:               [ Chef::Resource::ErlCall, Chef::Provider::ErlCall ],
+      execute:                [ Chef::Resource::Execute, Chef::Provider::Execute ],
+      file:                   [ Chef::Resource::File, Chef::Provider::File ],
+      gem_package:            [ Chef::Resource::GemPackage, Chef::Provider::Package::Rubygems ],
+      git:                    [ Chef::Resource::Git, Chef::Provider::Git ],
+      group:                  [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ],
+      homebrew_package:       [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ],
+      http_request:           [ Chef::Resource::HttpRequest, Chef::Provider::HttpRequest ],
+      ifconfig:               [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+      link:                   [ Chef::Resource::Link, Chef::Provider::Link ],
+      log:                    [ Chef::Resource::Log, Chef::Provider::Log::ChefLog ],
+      macports_package:       [ Chef::Resource::MacportsPackage, Chef::Provider::Package::Macports ],
+      mdadm:                  [ Chef::Resource::Mdadm, Chef::Provider::Mdadm ],
+      mount:                  [ Chef::Resource::Mount, Chef::Provider::Mount::Mount ],
+      perl:                   [ Chef::Resource::Perl, Chef::Provider::Script ],
+      portage_package:        [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ],
+      python:                 [ Chef::Resource::Python, Chef::Provider::Script ],
+      remote_directory:       [ Chef::Resource::RemoteDirectory, Chef::Provider::RemoteDirectory ],
+      route:                  [ Chef::Resource::Route, Chef::Provider::Route ],
+      ruby:                   [ Chef::Resource::Ruby, Chef::Provider::Script ],
+      ruby_block:             [ Chef::Resource::RubyBlock, Chef::Provider::RubyBlock ],
+      script:                 [ Chef::Resource::Script, Chef::Provider::Script ],
+      subversion:             [ Chef::Resource::Subversion, Chef::Provider::Subversion ],
+      template:               [ Chef::Resource::Template, Chef::Provider::Template ],
+      timestamped_deploy:     [ Chef::Resource::TimestampedDeploy, Chef::Provider::Deploy::Timestamped ],
+      user:                   [ Chef::Resource::User, Chef::Provider::User::Useradd ],
+      whyrun_safe_ruby_block: [ Chef::Resource::WhyrunSafeRubyBlock, Chef::Provider::WhyrunSafeRubyBlock ],
+
+      # We want to check that these are unsupported:
+      apt_package: nil,
+      bff_package: nil,
+      dpkg_package: nil,
+      dsc_script: nil,
+      ips_package: nil,
+      pacman_package: nil,
+      paludis_package: nil,
+      rpm_package: nil,
+      smartos_package: nil,
+      solaris_package: nil,
+      yum_package: nil,
+      windows_package: nil,
+      windows_service: nil,
+
+      "linux" => {
+        apt_package:     [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ],
+        dpkg_package:    [ Chef::Resource::DpkgPackage, Chef::Provider::Package::Dpkg ],
+        pacman_package:  [ Chef::Resource::PacmanPackage, Chef::Provider::Package::Pacman ],
+        paludis_package: [ Chef::Resource::PaludisPackage, Chef::Provider::Package::Paludis ],
+        rpm_package:     [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ],
+        yum_package:     [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ],
+
+        "debian" => {
+          ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Debian ],
+          package:  [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ],
+  #        service: [ Chef::Resource::DebianService, Chef::Provider::Service::Debian ],
+
+          "debian" => {
+            "7.0" => {
+            },
+            "6.0" => {
+              ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+  #            service: [ Chef::Resource::InsservService, Chef::Provider::Service::Insserv ],
+            },
+            "5.0" => {
+              ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+            },
+          },
+          "gcel" => {
+            "3.1.4" => {
+              ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+            },
+          },
+          "linaro" => {
+            "3.1.4" => {
+              ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+            },
+          },
+          "linuxmint" => {
+            "3.1.4" => {
+              ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+  #            service: [ Chef::Resource::UpstartService, Chef::Provider::Service::Upstart ],
+            },
+          },
+          "raspbian" => {
+            "3.1.4" => {
+              ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+            },
+          },
+          "ubuntu" => {
+            "11.10" => {
+            },
+            "10.04" => {
+              ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+            },
+          },
+        },
+
+        "arch" => {
+          # TODO should be Chef::Resource::PacmanPackage
+          package: [ Chef::Resource::Package, Chef::Provider::Package::Pacman ],
+
+          "arch" => {
+            "3.1.4" => {
+            }
+          },
+        },
+
+        "freebsd" => {
+          group: [ Chef::Resource::Group, Chef::Provider::Group::Pw ],
+          user:  [ Chef::Resource::User, Chef::Provider::User::Pw ],
+
+          "freebsd" => {
+            "3.1.4" => {
+            },
+          },
+        },
+        "suse" => {
+          group: [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ],
+          "suse" => {
+            "12.0" => {
+            },
+            %w(11.1 11.2 11.3) => {
+              group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ],
+            },
+          },
+          "opensuse" => {
+  #          service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
+            package: [ Chef::Resource::ZypperPackage, Chef::Provider::Package::Zypper ],
+            group:   [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
+            "12.3" => {
+            },
+            "12.2" => {
+              group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ],
+            },
+          },
+        },
+
+        "gentoo" => {
+          # TODO should be Chef::Resource::PortagePackage
+          package:         [ Chef::Resource::Package, Chef::Provider::Package::Portage ],
+          portage_package: [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ],
+  #        service: [ Chef::Resource::GentooService, Chef::Provider::Service::Gentoo ],
+
+          "gentoo" => {
+            "3.1.4" => {
+            },
+          },
+        },
+
+        "rhel" => {
+  #        service: [ Chef::Resource::SystemdService, Chef::Provider::Service::Systemd ],
+          package:  [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ],
+          ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Redhat ],
+
+          %w(amazon xcp xenserver ibm_powerkvm cloudlinux parallels) => {
+            "3.1.4" => {
+  #            service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
+            },
+          },
+          %w(redhat centos scientific oracle) => {
+            "7.0" => {
+            },
+            "6.0" => {
+  #            service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
+            },
+          },
+          "fedora" => {
+            "15.0" => {
+            },
+            "14.0" => {
+  #            service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
+            },
+          },
+        },
+
+      },
+
+      "darwin" => {
+        %w(mac_os_x mac_os_x_server) => {
+          group:   [ Chef::Resource::Group, Chef::Provider::Group::Dscl ],
+          package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ],
+          user:    [ Chef::Resource::User, Chef::Provider::User::Dscl ],
+
+          "mac_os_x" => {
+            "10.9.2" => {
+            },
+          },
+        },
+      },
+
+      "windows" => {
+        batch:             [ Chef::Resource::Batch, Chef::Provider::Batch ],
+        dsc_script:        [ Chef::Resource::DscScript, Chef::Provider::DscScript ],
+        env:               [ Chef::Resource::Env, Chef::Provider::Env::Windows ],
+        group:             [ Chef::Resource::Group, Chef::Provider::Group::Windows ],
+        mount:             [ Chef::Resource::Mount, Chef::Provider::Mount::Windows ],
+        package:           [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ],
+        powershell_script: [ Chef::Resource::PowershellScript, Chef::Provider::PowershellScript ],
+        service:           [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ],
+        user:              [ Chef::Resource::User, Chef::Provider::User::Windows ],
+        windows_package:   [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ],
+        windows_service:   [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ],
+
+        "windows" => {
+          %w(mswin mingw32 windows) => {
+            "10.9.2" => {
+            },
+          },
+        },
+      },
+
+      "aix" => {
+        bff_package: [ Chef::Resource::BffPackage, Chef::Provider::Package::Aix ],
+        cron: [ Chef::Resource::Cron, Chef::Provider::Cron::Aix ],
+        group: [ Chef::Resource::Group, Chef::Provider::Group::Aix ],
+        ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Aix ],
+        mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Aix ],
+        # TODO should be Chef::Resource::BffPackage
+        package: [ Chef::Resource::Package, Chef::Provider::Package::Aix ],
+        rpm_package: [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ],
+        user: [ Chef::Resource::User, Chef::Provider::User::Aix ],
+  #      service: [ Chef::Resource::AixService, Chef::Provider::Service::Aix ],
+
+        "aix" => {
+          "aix" => {
+            "5.6" => {
+            },
+          },
+        },
+      },
+
+      "hpux" => {
+        "hpux" => {
+          "hpux" => {
+            "3.1.4" => {
+              group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ]
+            }
+          }
+        }
+      },
+
+      "netbsd" => {
+        "netbsd" => {
+          "netbsd" => {
+            "3.1.4" => {
+              group: [ Chef::Resource::Group, Chef::Provider::Group::Groupmod ],
+            },
+          },
+        },
+      },
+
+      "openbsd" => {
+        group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
+        package: [ Chef::Resource::OpenbsdPackage, Chef::Provider::Package::Openbsd ],
+
+        "openbsd" => {
+          "openbsd" => {
+            "3.1.4" => {
+            },
+          },
+        },
+      },
+
+      "solaris2" => {
+        group:           [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
+        ips_package:     [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ],
+        package:         [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ],
+        mount:           [ Chef::Resource::Mount, Chef::Provider::Mount::Solaris ],
+        solaris_package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ],
+
+        "smartos" => {
+          smartos_package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ],
+          package:         [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ],
+
+          "smartos" => {
+            "3.1.4" => {
+            },
+          },
+        },
+
+        "solaris2" => {
+          "nexentacore" => {
+            "3.1.4" => {
+            },
+          },
+          "omnios" => {
+            "3.1.4" => {
+              user: [ Chef::Resource::User, Chef::Provider::User::Solaris ],
+            }
+          },
+          "openindiana" => {
+            "3.1.4" => {
+            },
+          },
+          "opensolaris" => {
+            "3.1.4" => {
+            },
+          },
+          "solaris2" => {
+            user: [ Chef::Resource::User, Chef::Provider::User::Solaris ],
+            "5.11" => {
+              package: [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ],
+            },
+            "5.9" => {
+            },
+          },
+        },
+
+      },
+
+      "solaris" => {
+        "solaris" => {
+          "solaris" => {
+            "3.1.4" => {
+            },
+          },
+        },
+      },
+
+      "exherbo" => {
+        "exherbo" => {
+          "exherbo" => {
+            "3.1.4" => {
+              # TODO should be Chef::Resource::PaludisPackage
+              package: [ Chef::Resource::Package, Chef::Provider::Package::Paludis ]
+            }
+          }
+        }
       }
-
-    describe "on Ubuntu 14.04" do
-      let(:os) { "linux" }
-      let(:platform) { "ubuntu" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "14.04" }
-
-      supported_providers = [
-        :apt_package, :bash, :breakpoint, :chef_gem, :cookbook_file, :csh, :deploy,
-        :deploy_revision, :directory, :dpkg_package, :easy_install_package, :erl_call,
-        :execute, :file, :gem_package, :git, :homebrew_package, :http_request, :link,
-        :log, :macports_package, :pacman_package, :paludis_package, :perl, :python,
-        :remote_directory, :route, :rpm_package, :ruby, :ruby_block, :script, :subversion,
-        :template, :timestamped_deploy, :whyrun_safe_ruby_block, :yum_package,
-      ]
-
-      supported_providers.each do |static_resource|
-        static_provider = static_mapping[static_resource]
-        context "when the resource is a #{static_resource}" do
-          let(:resource) { double(Chef::Resource, provider: nil, resource_name: static_resource) }
-          let(:action) { :start }  # in reality this doesn't matter much
-          it "should resolve to a #{static_provider} provider" do
-            expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup)
-            expect(resolved_provider).to eql(static_provider)
-          end
+    }
+
+    def self.create_provider_tests(providers, test, expected, filter)
+      expected = expected.merge(providers.select { |key, value| key.is_a?(Symbol) })
+      providers.each do |key, value|
+        if !key.is_a?(Symbol)
+          next_test = test.merge({ filter => key })
+          next_filter =
+            case filter
+            when :os
+              :platform_family
+            when :platform_family
+              :platform
+            when :platform
+              :platform_version
+            when :platform_version
+              nil
+            else
+              raise "Hash too deep; only os, platform_family, platform and platform_version supported"
+            end
+          create_provider_tests(value, next_test, expected, next_filter)
         end
       end
-
-      unsupported_providers = [
-        :bff_package, :dsc_script, :ips_package, :smartos_package,
-        :solaris_package, :windows_package, :windows_service,
-      ]
-
-      unsupported_providers.each do |static_resource|
-        static_provider = static_mapping[static_resource]
-        context "when the resource is a #{static_resource}" do
-          let(:resource) { double(Chef::Resource, provider: nil, resource_name: static_resource) }
-          let(:action) { :start }  # in reality this doesn't matter much
-          it "should fall back into the old provider mapper code and hooks" do
-            retval = Object.new
-            expect(provider_resolver).to receive(:maybe_chef_platform_lookup).and_return(retval)
-            expect(resolved_provider).to equal(retval)
-          end
+      # If there is no filter, we're as deep as we need to go
+      if !filter
+        on_platform test.delete(:platform), test do
+          expect_providers(expected)
         end
       end
     end
+
+    create_provider_tests(PROVIDERS, {}, {}, :os)
   end
 end
diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb
index 5a21b09..97b88b1 100644
--- a/spec/unit/provider_spec.rb
+++ b/spec/unit/provider_spec.rb
@@ -49,6 +49,13 @@ class ConvergeActionDemonstrator < Chef::Provider
   end
 end
 
+class CheckResourceSemanticsDemonstrator < ConvergeActionDemonstrator
+  def check_resource_semantics!
+    raise Chef::Exceptions::InvalidResourceSpecification.new("check_resource_semantics!")
+  end
+end
+
+
 describe Chef::Provider do
   before(:each) do
     @cookbook_collection = Chef::CookbookCollection.new([])
@@ -89,6 +96,10 @@ describe Chef::Provider do
     expect(@provider.send(:whyrun_supported?)).to eql(false)
   end
 
+  it "should do nothing for check_resource_semantics! by default" do
+    expect { @provider.check_resource_semantics! }.not_to raise_error
+  end
+
   it "should return true for action_nothing" do
     expect(@provider.action_nothing).to eql(true)
   end
@@ -103,9 +114,7 @@ describe Chef::Provider do
   end
 
   it "does not re-load recipes when creating the temporary run context" do
-    # we actually want to test that RunContext#load is never called, but we
-    # can't stub all instances of an object with rspec's mocks. :/
-    allow(Chef::RunContext).to receive(:new).and_raise("not supposed to happen")
+    expect_any_instance_of(Chef::RunContext).not_to receive(:load)
     snitch = Proc.new {temporary_collection = @run_context.resource_collection}
     @provider.send(:recipe_eval, &snitch)
   end
@@ -176,6 +185,15 @@ describe Chef::Provider do
         expect(@resource).not_to be_updated_by_last_action
       end
     end
+
+    describe "and the resource is invalid" do
+      let(:provider) { CheckResourceSemanticsDemonstrator.new(@resource, @run_context) }
+
+      it "fails with InvalidResourceSpecification when run" do
+        expect { provider.run_action(:foo) }.to raise_error(Chef::Exceptions::InvalidResourceSpecification)
+      end
+
+    end
   end
 
 end
diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb
index 7442f44..34f852e 100644
--- a/spec/unit/recipe_spec.rb
+++ b/spec/unit/recipe_spec.rb
@@ -3,7 +3,7 @@
 # Author:: Christopher Walters (<cw at opscode.com>)
 # Author:: Tim Hinderliter (<tim at opscode.com>)
 # Author:: Seth Chisamore (<schisamo at opscode.com>)
-# Copyright:: Copyright (c) 2008-2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,25 +24,19 @@ require 'chef/platform/resource_priority_map'
 
 describe Chef::Recipe do
 
-  let(:cookbook_repo) { File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks")) }
-
-  let(:cookbook_loader) do
-    loader = Chef::CookbookLoader.new(cookbook_repo)
-    loader.load_cookbooks
-    loader
+  let(:cookbook_collection) do
+    cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks"))
+    cookbook_loader = Chef::CookbookLoader.new(cookbook_repo)
+    cookbook_loader.load_cookbooks
+    Chef::CookbookCollection.new(cookbook_loader)
   end
 
-  let(:cookbook_collection) { Chef::CookbookCollection.new(cookbook_loader) }
-
   let(:node) do
-    Chef::Node.new.tap {|n| n.normal[:tags] = [] }
-  end
-
-  let(:events) do
-    Chef::EventDispatch::Dispatcher.new
+    Chef::Node.new
   end
 
   let(:run_context) do
+    events = Chef::EventDispatch::Dispatcher.new
     Chef::RunContext.new(node, cookbook_collection, events)
   end
 
@@ -83,7 +77,7 @@ describe Chef::Recipe do
       it "should require a name argument" do
         expect {
           recipe.cat
-        }.to raise_error(ArgumentError, "You must supply a name when declaring a cat resource")
+        }.to raise_error(ArgumentError)
       end
 
       it "should allow regular errors (not NameErrors) to pass unchanged" do
@@ -121,7 +115,8 @@ describe Chef::Recipe do
 
         it "locate resource for particular platform" do
           ShaunTheSheep = Class.new(Chef::Resource)
-          ShaunTheSheep.provides :laughter, :on_platforms => ["television"]
+          ShaunTheSheep.resource_name :shaun_the_sheep
+          ShaunTheSheep.provides :laughter, :platform => ["television"]
           node.automatic[:platform] = "television"
           node.automatic[:platform_version] = "123"
           res = recipe.laughter "timmy"
@@ -131,6 +126,7 @@ describe Chef::Recipe do
 
         it "locate a resource for all platforms" do
           YourMom = Class.new(Chef::Resource)
+          YourMom.resource_name :your_mom
           YourMom.provides :love_and_caring
           res = recipe.love_and_caring "mommy"
           expect(res.name).to eql("mommy")
@@ -141,9 +137,9 @@ describe Chef::Recipe do
           before do
             node.automatic[:platform] = "nbc_sports"
             Sounders = Class.new(Chef::Resource)
-            Sounders.provides :football, platform: "nbc_sports"
+            Sounders.resource_name :sounders
             TottenhamHotspur = Class.new(Chef::Resource)
-            TottenhamHotspur.provides :football, platform: "nbc_sports"
+            TottenhamHotspur.resource_name :tottenham_hotspur
           end
 
           after do
@@ -151,25 +147,20 @@ describe Chef::Recipe do
             Object.send(:remove_const, :TottenhamHotspur)
           end
 
-          it "warns if resolution of the two resources is ambiguous" do
-            expect(Chef::Log).to receive(:warn).at_least(:once).with(/Ambiguous resource precedence/)
-            res1 = recipe.football "club world cup"
-            expect(res1.name).to eql("club world cup")
-            # the class of res1 is not defined.
-          end
+          it "selects the first one alphabetically" do
+            Sounders.provides :football, platform: "nbc_sports"
+            TottenhamHotspur.provides :football, platform: "nbc_sports"
 
-          it "selects one if it is given priority" do
-            expect(Chef::Log).not_to receive(:warn)
-            Chef::Platform::ResourcePriorityMap.instance.send(:priority, :football, TottenhamHotspur, platform: "nbc_sports")
             res1 = recipe.football "club world cup"
             expect(res1.name).to eql("club world cup")
-            expect(res1).to be_a_kind_of(TottenhamHotspur)
+            expect(res1).to be_a_kind_of(Sounders)
           end
 
-          it "selects the other one if it is given priority" do
-            expect(Chef::Log).not_to receive(:warn)
-            Chef::Platform::ResourcePriorityMap.instance.send(:priority, :football, Sounders, platform: "nbc_sports")
-            res1 = recipe.football "club world cup"
+          it "selects the first one alphabetically even if the declaration order is reversed" do
+            TottenhamHotspur.provides :football2, platform: "nbc_sports"
+            Sounders.provides :football2, platform: "nbc_sports"
+
+            res1 = recipe.football2 "club world cup"
             expect(res1.name).to eql("club world cup")
             expect(res1).to be_a_kind_of(Sounders)
           end
@@ -308,6 +299,34 @@ describe Chef::Recipe do
         zm_resource # force let binding evaluation
         expect(run_context.resource_collection.resources(:zen_master => "klopp")).to eq(zm_resource)
       end
+
+      it "will insert another resource if create_if_missing is not set (cloned resource as of Chef-12)" do
+        zm_resource
+        recipe.declare_resource(:zen_master, "klopp")
+        expect(run_context.resource_collection.count).to eql(2)
+      end
+
+      it "does not insert two resources if create_if_missing is used" do
+        zm_resource
+        recipe.declare_resource(:zen_master, "klopp", create_if_missing: true)
+        expect(run_context.resource_collection.count).to eql(1)
+      end
+
+      context "injecting a different run_context" do
+        let(:run_context2) do
+          events = Chef::EventDispatch::Dispatcher.new
+          Chef::RunContext.new(node, cookbook_collection, events)
+        end
+
+        it "should insert resources into the correct run_context" do
+          zm_resource
+          recipe.declare_resource(:zen_master, "klopp2", run_context: run_context2)
+          run_context2.resource_collection.lookup("zen_master[klopp2]")
+          expect {run_context2.resource_collection.lookup("zen_master[klopp]")}.to raise_error(Chef::Exceptions::ResourceNotFound)
+          expect {run_context.resource_collection.lookup("zen_master[klopp2]")}.to raise_error(Chef::Exceptions::ResourceNotFound)
+          run_context.resource_collection.lookup("zen_master[klopp]")
+        end
+      end
     end
 
     describe "creating a resource with short name" do
@@ -409,7 +428,7 @@ describe Chef::Recipe do
 
       it "does not copy the action from the first resource" do
         expect(original_resource.action).to eq([:score])
-        expect(duplicated_resource.action).to eq(:nothing)
+        expect(duplicated_resource.action).to eq([:nothing])
       end
 
       it "does not copy the source location of the first resource" do
@@ -577,6 +596,36 @@ describe Chef::Recipe do
       expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context)
       openldap_recipe.include_recipe "::default"
     end
+
+    it "will not load a recipe twice when called first from an LWRP provider" do
+      openldap_recipe = Chef::Recipe.new("openldap", "test", run_context)
+      expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once)
+      allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false)
+      expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context)
+      openldap_recipe.include_recipe "::default"
+      expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context)
+      openldap_recipe.openldap_includer("do it").run_action(:run)
+    end
+
+    it "will not load a recipe twice when called last from an LWRP provider" do
+      openldap_recipe = Chef::Recipe.new("openldap", "test", run_context)
+      expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once)
+      allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false)
+      expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context)
+      openldap_recipe.openldap_includer("do it").run_action(:run)
+      expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context)
+      openldap_recipe.include_recipe "::default"
+    end
+
+    it "will not load a recipe twice when called both times from an LWRP provider" do
+      openldap_recipe = Chef::Recipe.new("openldap", "test", run_context)
+      expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once)
+      allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false)
+      expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context)
+      openldap_recipe.openldap_includer("do it").run_action(:run)
+      expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context)
+      openldap_recipe.openldap_includer("do it").run_action(:run)
+    end
   end
 
   describe "tags" do
@@ -588,21 +637,25 @@ describe Chef::Recipe do
       end
     end
 
+    it "should initialize tags to an empty Array" do
+      expect(node.tags).to eql([])
+    end
+
     it "should set tags via tag" do
       recipe.tag "foo"
-      expect(node[:tags]).to include("foo")
+      expect(node.tags).to include("foo")
     end
 
     it "should set multiple tags via tag" do
       recipe.tag "foo", "bar"
-      expect(node[:tags]).to include("foo")
-      expect(node[:tags]).to include("bar")
+      expect(node.tags).to include("foo")
+      expect(node.tags).to include("bar")
     end
 
     it "should not set the same tag twice via tag" do
       recipe.tag "foo"
       recipe.tag "foo"
-      expect(node[:tags]).to eql([ "foo" ])
+      expect(node.tags).to eql([ "foo" ])
     end
 
     it "should return the current list of tags from tag with no arguments" do
@@ -626,13 +679,13 @@ describe Chef::Recipe do
     it "should remove a tag from the tag list via untag" do
       recipe.tag "foo"
       recipe.untag "foo"
-      expect(node[:tags]).to eql([])
+      expect(node.tags).to eql([])
     end
 
     it "should remove multiple tags from the tag list via untag" do
       recipe.tag "foo", "bar"
       recipe.untag "bar", "foo"
-      expect(node[:tags]).to eql([])
+      expect(node.tags).to eql([])
     end
   end
 
diff --git a/spec/unit/registry_helper_spec.rb b/spec/unit/registry_helper_spec.rb
deleted file mode 100644
index 036a083..0000000
--- a/spec/unit/registry_helper_spec.rb
+++ /dev/null
@@ -1,376 +0,0 @@
-#
-# Author:: Prajakta Purohit (prajakta at opscode.com)
-# Copyright:: Copyright (c) 2012 Opscode, 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 'spec_helper'
-
-describe Chef::Provider::RegistryKey do
-
-  let(:value1) { { :name => "one", :type => :string, :data => "1" } }
-  let(:key_path) { 'HKCU\Software\OpscodeNumbers' }
-  let(:key) { 'Software\OpscodeNumbers' }
-  let(:key_parent) { 'Software' }
-  let(:key_to_delete) { 'OpscodeNumbers' }
-  let(:sub_key) {'OpscodePrimes'}
-  let(:missing_key_path) {'HKCU\Software'}
-
-  before(:each) do
-    allow_any_instance_of(Chef::Win32::Registry).to receive(:machine_architecture).and_return(:x86_64)
-    @registry = Chef::Win32::Registry.new()
-
-    #Making the values for registry constants available on unix
-    Object.send(:remove_const, 'Win32') if defined?(Win32)
-    Win32 = Module.new
-    Win32::Registry = Class.new
-    Win32::Registry::KEY_SET_VALUE = 0x0002
-    Win32::Registry::KEY_QUERY_VALUE = 0x0001
-    Win32::Registry::KEY_WRITE = 0x00020000 | 0x0002 | 0x0004
-    Win32::Registry::KEY_READ = 0x00020000 | 0x0001 | 0x0008 | 0x0010
-
-    Win32::Registry::Error = Class.new(RuntimeError)
-
-    @hive_mock = double("::Win32::Registry::HKEY_CURRENT_USER")
-    @reg_mock = double("reg")
-  end
-
-  describe "get_values" do
-    it "gets all values for a key if the key exists" do
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@reg_mock).to receive(:map)
-      @registry.get_values(key_path)
-    end
-
-    it "throws an exception if key does not exist" do
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
-      expect{@registry.get_values(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
-    end
-  end
-
-  describe "set_value" do
-    it "does nothing if key and hive and value exist" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
-      expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true)
-      @registry.set_value(key_path, value1)
-    end
-
-    it "updates value if key and hive and value exist, but data is different" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
-      expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(false)
-      expect(@hive_mock).to receive(:open).with(key, Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1)
-      expect(@reg_mock).to receive(:write).with("one", 1, "1")
-      @registry.set_value(key_path, value1)
-    end
-
-    it "creates value if the key exists and the value does not exist" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false)
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1)
-      expect(@reg_mock).to receive(:write).with("one", 1, "1")
-      @registry.set_value(key_path, value1)
-    end
-
-    it "should raise an exception if the key does not exist" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
-      expect {@registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
-    end
-  end
-
-  describe "delete_value" do
-    it "deletes value if value exists" do
-      expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@reg_mock).to receive(:delete_value).with("one").and_return(true)
-      @registry.delete_value(key_path, value1)
-    end
-
-    it "raises an exception if the key does not exist" do
-      expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
-      @registry.delete_value(key_path, value1)
-    end
-
-    it "does nothing if the value does not exist" do
-      expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false)
-      @registry.delete_value(key_path, value1)
-    end
-  end
-
-  describe "create_key" do
-    it "creates key if intermediate keys are missing and recursive is set to true" do
-      expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true)
-      expect(@registry).to receive(:create_missing).with(key_path)
-      expect(@registry).to receive(:key_exists?).with(key_path).and_return(false)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture)
-      @registry.create_key(key_path, true)
-    end
-
-    it "raises an exception if intermediate keys are missing and recursive is set to false" do
-      expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true)
-      expect{@registry.create_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive)
-    end
-
-    it "does nothing if the key exists" do
-      expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true)
-      expect(@registry).to receive(:create_missing).with(key_path)
-      expect(@registry).to receive(:key_exists?).with(key_path).and_return(true)
-      @registry.create_key(key_path, true)
-    end
-
-    it "create key if intermediate keys not missing and recursive is set to false" do
-      expect(@registry).to receive(:keys_missing?).with(key_path).and_return(false)
-      expect(@registry).to receive(:key_exists?).with(key_path).and_return(false)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture)
-      @registry.create_key(key_path, false)
-    end
-
-    it "create key if intermediate keys not missing and recursive is set to true" do
-      expect(@registry).to receive(:keys_missing?).with(key_path).and_return(false)
-      expect(@registry).to receive(:key_exists?).with(key_path).and_return(false)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture)
-      @registry.create_key(key_path, true)
-    end
-  end
-
-  describe "delete_key", :windows_only do
-    it "deletes key if it has subkeys and recursive is set to true" do
-      expect(@registry).to receive(:key_exists?).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_subkeys).with(key_path).and_return([sub_key])
-      expect(@registry).to receive(:key_exists?).with(key_path+"\\"+sub_key).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path+"\\"+sub_key).and_return([@hive_mock, key+"\\"+sub_key])
-      expect(@registry).to receive(:has_subkeys?).with(key_path+"\\"+sub_key).and_return(false)
-      expect(@registry).to receive(:delete_key_ex).twice
-      @registry.delete_key(key_path, true)
-    end
-
-    it "raises an exception if it has subkeys but recursive is set to false" do
-      expect(@registry).to receive(:key_exists?).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true)
-      expect{@registry.delete_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive)
-    end
-
-    it "deletes key if the key exists and has no subkeys" do
-      expect(@registry).to receive(:key_exists?).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(false)
-      expect(@registry).to receive(:delete_key_ex)
-      @registry.delete_key(key_path, true)
-    end
-  end
-
-  describe "key_exists?" do
-    it "returns true if key_exists" do
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@registry.key_exists?(key_path)).to eq(true)
-    end
-
-    it "returns false if key does not exist" do
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_raise(::Win32::Registry::Error)
-      expect(@registry.key_exists?(key_path)).to eq(false)
-    end
-  end
-
-  describe "key_exists!" do
-    it "throws an exception if the key_parent does not exist" do
-      expect(@registry).to receive(:key_exists?).with(key_path).and_return(false)
-      expect{@registry.key_exists!(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
-    end
-  end
-
-  describe "hive_exists?" do
-    it "returns true if the hive exists" do
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      @registry.hive_exists?(key_path) == true
-    end
-
-    it "returns false if the hive does not exist" do
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegHiveMissing)
-      @registry.hive_exists?(key_path) == false
-    end
-  end
-
-  describe "has_subkeys?" do
-    it "returns true if the key has subkeys" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@reg_mock).to receive(:each_key).and_yield(key)
-      @registry.has_subkeys?(key_path) == true
-    end
-
-    it "returns false if the key does not have subkeys" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@reg_mock).to receive(:each_key).and_return(no_args())
-      expect(@registry.has_subkeys?(key_path)).to eq(false)
-    end
-
-    it "throws an exception if the key does not exist" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
-      expect {@registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
-    end
-  end
-
-  describe "get_subkeys" do
-    it "returns the subkeys if they exist" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@reg_mock).to receive(:each_key).and_yield(sub_key)
-      @registry.get_subkeys(key_path)
-    end
-  end
-
-  describe "value_exists?" do
-    it "throws an exception if the key does not exist" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
-      expect {@registry.value_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
-    end
-
-    it "returns true if the value exists" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@reg_mock).to receive(:any?).and_yield("one")
-      @registry.value_exists?(key_path, value1) == true
-    end
-
-    it "returns false if the value does not exist" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@reg_mock).to receive(:any?).and_yield(no_args())
-      @registry.value_exists?(key_path, value1) == false
-    end
-  end
-
-  describe "data_exists?" do
-    it "throws an exception if the key does not exist" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
-      expect {@registry.data_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
-    end
-
-    it "returns true if the data exists" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1)
-      expect(@reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "1")
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@registry.data_exists?(key_path, value1)).to eq(true)
-    end
-
-    it "returns false if the data does not exist" do
-      expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1)
-      expect(@reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "2")
-      expect(@registry.data_exists?(key_path, value1)).to eq(false)
-    end
-  end
-
-  describe "value_exists!" do
-    it "does nothing if the value exists" do
-      expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
-      @registry.value_exists!(key_path, value1)
-    end
-
-    it "throws an exception if the value does not exist" do
-      expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false)
-      expect{@registry.value_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing)
-    end
-  end
-
-  describe "data_exists!" do
-    it "does nothing if the data exists" do
-      expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true)
-      @registry.data_exists!(key_path, value1)
-    end
-
-    it "throws an exception if the data does not exist" do
-      expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(false)
-      expect{@registry.data_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegDataMissing)
-    end
-  end
-
-  describe "type_matches?" do
-    it "returns true if type matches" do
-      expect(@registry).to receive(:value_exists!).with(key_path, value1).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1)
-      expect(@reg_mock).to receive(:each).and_yield("one", 1)
-      expect(@registry.type_matches?(key_path, value1)).to eq(true)
-    end
-
-    it "returns false if type does not match" do
-      expect(@registry).to receive(:value_exists!).with(key_path, value1).and_return(true)
-      expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
-      expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
-      expect(@reg_mock).to receive(:each).and_yield("two", 2)
-      expect(@registry.type_matches?(key_path, value1)).to eq(false)
-    end
-
-    it "throws an exception if value does not exist" do
-      expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false)
-      expect{@registry.type_matches?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing)
-    end
-  end
-
-  describe "type_matches!" do
-    it "does nothing if the type_matches" do
-      expect(@registry).to receive(:type_matches?).with(key_path, value1).and_return(true)
-      @registry.type_matches!(key_path, value1)
-    end
-
-    it "throws an exception if the type does not match" do
-      expect(@registry).to receive(:type_matches?).with(key_path, value1).and_return(false)
-      expect{@registry.type_matches!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegTypesMismatch)
-    end
-  end
-
-  describe "keys_missing?" do
-    it "returns true if the keys are missing" do
-      expect(@registry).to receive(:key_exists?).with(missing_key_path).and_return(false)
-      expect(@registry.keys_missing?(key_path)).to eq(true)
-    end
-
-    it "returns false if no keys in the path are missing" do
-      expect(@registry).to receive(:key_exists?).with(missing_key_path).and_return(true)
-      expect(@registry.keys_missing?(key_path)).to eq(false)
-    end
-  end
-end
diff --git a/spec/unit/resource/batch_spec.rb b/spec/unit/resource/batch_spec.rb
index 4a056b8..b8c2897 100644
--- a/spec/unit/resource/batch_spec.rb
+++ b/spec/unit/resource/batch_spec.rb
@@ -25,6 +25,7 @@ describe Chef::Resource::Batch do
 
     node.default["kernel"] = Hash.new
     node.default["kernel"][:machine] = :x86_64.to_s
+    node.automatic[:os] = 'windows'
 
     run_context = Chef::RunContext.new(node, nil, nil)
 
diff --git a/spec/unit/resource/breakpoint_spec.rb b/spec/unit/resource/breakpoint_spec.rb
index ed1f3eb..88ab34d 100644
--- a/spec/unit/resource/breakpoint_spec.rb
+++ b/spec/unit/resource/breakpoint_spec.rb
@@ -37,7 +37,7 @@ describe Chef::Resource::Breakpoint do
   end
 
   it "defaults to the break action" do
-    expect(@breakpoint.action).to eq("break")
+    expect(@breakpoint.action).to eq([:break])
   end
 
   it "names itself after the line number of the file where it's created" do
diff --git a/spec/unit/resource/chef_gem_spec.rb b/spec/unit/resource/chef_gem_spec.rb
index 7352a8f..fe78807 100644
--- a/spec/unit/resource/chef_gem_spec.rb
+++ b/spec/unit/resource/chef_gem_spec.rb
@@ -52,7 +52,7 @@ describe Chef::Resource::ChefGem, "gem_binary" do
 
   context "when building the resource" do
     let(:node) do
-      Chef::Node.new.tap {|n| n.normal[:tags] = [] }
+      Chef::Node.new
     end
 
     let(:run_context) do
diff --git a/spec/unit/resource/cron_spec.rb b/spec/unit/resource/cron_spec.rb
index 743552c..0978be6 100644
--- a/spec/unit/resource/cron_spec.rb
+++ b/spec/unit/resource/cron_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::Cron do
   end
 
   it "should have a default action of 'create'" do
-    expect(@resource.action).to eql(:create)
+    expect(@resource.action).to eql([:create])
   end
 
   it "should accept create or delete for action" do
diff --git a/spec/unit/resource/deploy_spec.rb b/spec/unit/resource/deploy_spec.rb
index 0403a7b..5b6a452 100644
--- a/spec/unit/resource/deploy_spec.rb
+++ b/spec/unit/resource/deploy_spec.rb
@@ -148,10 +148,16 @@ describe Chef::Resource::Deploy do
     expect(@resource.current_path).to eql("/my/deploy/dir/current")
   end
 
+  it "allows depth to be set via integer" do
+    expect(@resource.depth).to be_nil
+    @resource.depth 1
+    expect(@resource.depth).to eql(1)
+  end
+
   it "gives #depth as 5 if shallow clone is true, nil otherwise" do
     expect(@resource.depth).to be_nil
     @resource.shallow_clone true
-    expect(@resource.depth).to eql("5")
+    expect(@resource.depth).to eql(5)
   end
 
   it "aliases repo as repository" do
diff --git a/spec/unit/resource/directory_spec.rb b/spec/unit/resource/directory_spec.rb
index c452b2a..e9e8080 100644
--- a/spec/unit/resource/directory_spec.rb
+++ b/spec/unit/resource/directory_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::Directory do
   end
 
   it "should have a default action of 'create'" do
-    expect(@resource.action).to eql(:create)
+    expect(@resource.action).to eql([:create])
   end
 
   it "should accept create or delete for action" do
diff --git a/spec/unit/resource/dsc_resource_spec.rb b/spec/unit/resource/dsc_resource_spec.rb
index ae15f56..276aa74 100644
--- a/spec/unit/resource/dsc_resource_spec.rb
+++ b/spec/unit/resource/dsc_resource_spec.rb
@@ -15,13 +15,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
 require 'spec_helper'
-
 describe Chef::Resource::DscResource do
   let(:dsc_test_resource_name) { 'DSCTest' }
   let(:dsc_test_property_name) { :DSCTestProperty }
   let(:dsc_test_property_value) { 'DSCTestValue' }
+  let(:dsc_test_reboot_action) { :reboot_now }
+  let(:dsc_test_timeout) { 101 }
 
   context 'when Powershell supports Dsc' do
     let(:dsc_test_run_context) {
@@ -30,15 +30,16 @@ describe Chef::Resource::DscResource do
       empty_events = Chef::EventDispatch::Dispatcher.new
       Chef::RunContext.new(node, {}, empty_events)
     }
+
     let(:dsc_test_resource) {
       Chef::Resource::DscResource.new(dsc_test_resource_name, dsc_test_run_context)
     }
 
     it "has a default action of `:run`" do
-      expect(dsc_test_resource.action).to eq(:run)
+      expect(dsc_test_resource.action).to eq([:run])
     end
 
-    it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do
+    it "has an ed_actions attribute with only the `:run` and `:nothing` attributes" do
       expect(dsc_test_resource.allowed_actions.to_set).to eq([:run,:nothing].to_set)
     end
 
@@ -52,6 +53,16 @@ describe Chef::Resource::DscResource do
       expect(dsc_test_resource.module_name).to eq(dsc_test_resource_name)
     end
 
+    it "allows the reboot_action attribute to be set" do
+      dsc_test_resource.reboot_action(dsc_test_reboot_action)
+      expect(dsc_test_resource.reboot_action).to eq(dsc_test_reboot_action)
+    end
+
+    it "allows the timeout attribute to be set" do
+      dsc_test_resource.timeout(dsc_test_timeout)
+      expect(dsc_test_resource.timeout).to eq(dsc_test_timeout)
+    end
+
     context "when setting a dsc property" do
       it "allows setting a dsc property with a property name of type Symbol" do
         dsc_test_resource.property(dsc_test_property_name, dsc_test_property_value)
diff --git a/spec/unit/resource/dsc_script_spec.rb b/spec/unit/resource/dsc_script_spec.rb
index 71103ea..1fa865a 100644
--- a/spec/unit/resource/dsc_script_spec.rb
+++ b/spec/unit/resource/dsc_script_spec.rb
@@ -29,7 +29,7 @@ describe Chef::Resource::DscScript do
       Chef::RunContext.new(node, {}, empty_events)
     }
     let(:dsc_test_resource) {
-      Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) 
+      Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context)
     }
     let(:configuration_code) {'echo "This is supposed to create a configuration document."'}
     let(:configuration_path) {'c:/myconfigs/formatc.ps1'}
@@ -38,7 +38,7 @@ describe Chef::Resource::DscScript do
     let(:configuration_data_script) { 'c:/myconfigs/data/safedata.psd1' }
 
     it "has a default action of `:run`" do
-      expect(dsc_test_resource.action).to eq(:run)
+      expect(dsc_test_resource.action).to eq([:run])
     end
 
     it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do
@@ -70,6 +70,10 @@ describe Chef::Resource::DscScript do
       expect(dsc_test_resource.configuration_data_script).to eq(configuration_data_script)
     end
 
+    it "has the ps_credential helper method" do
+      expect(dsc_test_resource).to respond_to(:ps_credential)
+    end
+
     context "when calling imports" do
       let(:module_name)   { 'FooModule' }
       let(:module_name_b)   { 'BarModule' }
diff --git a/spec/unit/resource/env_spec.rb b/spec/unit/resource/env_spec.rb
index 566827a..9bee07c 100644
--- a/spec/unit/resource/env_spec.rb
+++ b/spec/unit/resource/env_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::Env do
   end
 
   it "should have a default action of 'create'" do
-    expect(@resource.action).to eql(:create)
+    expect(@resource.action).to eql([:create])
   end
 
   { :create => false, :delete => false, :modify => false, :flibber => true }.each do |action,bad_value|
diff --git a/spec/unit/resource/erl_call_spec.rb b/spec/unit/resource/erl_call_spec.rb
index 8ec1826..9abf2e7 100644
--- a/spec/unit/resource/erl_call_spec.rb
+++ b/spec/unit/resource/erl_call_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::ErlCall do
   end
 
   it "should have a default action of run" do
-    expect(@resource.action).to eql("run")
+    expect(@resource.action).to eql([:run])
   end
 
   it "should accept run as an action" do
diff --git a/spec/unit/resource/file/verification_spec.rb b/spec/unit/resource/file/verification_spec.rb
index 3609d9d..6b92978 100644
--- a/spec/unit/resource/file/verification_spec.rb
+++ b/spec/unit/resource/file/verification_spec.rb
@@ -69,12 +69,40 @@ describe Chef::Resource::File::Verification do
     end
 
     context "with a verification command(String)" do
+      before(:each) do
+        allow(Chef::Log).to receive(:deprecation).and_return(nil)
+      end
+
+      def platform_specific_verify_command(variable_name)
+        if windows?
+          "if \"#{temp_path}\" == \"%{#{variable_name}}\" (exit 0) else (exit 1)"
+        else
+          "test #{temp_path} = %{#{variable_name}}"
+        end
+      end
+
       it "substitutes \%{file} with the path" do
-        test_command = if windows?
-                         "if \"#{temp_path}\" == \"%{file}\" (exit 0) else (exit 1)"
-                       else
-                         "test #{temp_path} = %{file}"
-                       end
+        test_command = platform_specific_verify_command('file')
+        v = Chef::Resource::File::Verification.new(parent_resource, test_command, {})
+        expect(v.verify(temp_path)).to eq(true)
+      end
+
+      it "warns about deprecation when \%{file} is used" do
+        expect(Chef::Log).to receive(:deprecation).with(/%{file} is deprecated/, /verification_spec\.rb/)
+        test_command = platform_specific_verify_command('file')
+        Chef::Resource::File::Verification.new(parent_resource, test_command, {})
+          .verify(temp_path)
+      end
+
+      it "does not warn about deprecation when \%{file} is not used" do
+        expect(Chef::Log).to_not receive(:deprecation)
+        test_command = platform_specific_verify_command('path')
+        Chef::Resource::File::Verification.new(parent_resource, test_command, {})
+          .verify(temp_path)
+      end
+
+      it "substitutes \%{path} with the path" do
+        test_command = platform_specific_verify_command('path')
         v = Chef::Resource::File::Verification.new(parent_resource, test_command, {})
         expect(v.verify(temp_path)).to eq(true)
       end
diff --git a/spec/unit/resource/file_spec.rb b/spec/unit/resource/file_spec.rb
index db52e35..76beaf1 100644
--- a/spec/unit/resource/file_spec.rb
+++ b/spec/unit/resource/file_spec.rb
@@ -29,7 +29,7 @@ describe Chef::Resource::File do
   end
 
   it "should have a default action of 'create'" do
-    expect(@resource.action).to eql("create")
+    expect(@resource.action).to eql([:create])
   end
 
   it "should have a default content of nil" do
diff --git a/spec/unit/resource/group_spec.rb b/spec/unit/resource/group_spec.rb
index bcf9205..a4029fc 100644
--- a/spec/unit/resource/group_spec.rb
+++ b/spec/unit/resource/group_spec.rb
@@ -50,7 +50,7 @@ describe Chef::Resource::Group, "initialize" do
   end
 
   it "should set action to :create" do
-    expect(@resource.action).to eql(:create)
+    expect(@resource.action).to eql([:create])
   end
 
   %w{create remove modify manage}.each do |action|
diff --git a/spec/unit/resource/ifconfig_spec.rb b/spec/unit/resource/ifconfig_spec.rb
index ea5282a..e3e1f6d 100644
--- a/spec/unit/resource/ifconfig_spec.rb
+++ b/spec/unit/resource/ifconfig_spec.rb
@@ -47,21 +47,23 @@ describe Chef::Resource::Ifconfig do
     end
   end
 
-  shared_examples "being a platform using the default ifconfig provider" do |platform, version|
+  shared_examples "being a platform based on an old Debian" do |platform, version|
     before do
+      @node.automatic_attrs[:os] = 'linux'
+      @node.automatic_attrs[:platform_family] = 'debian'
       @node.automatic_attrs[:platform] = platform
       @node.automatic_attrs[:platform_version] = version
     end
 
     it "should use an ordinary Provider::Ifconfig as a provider for #{platform} #{version}" do
-      expect(@resource.provider_for_action(:add)).to be_a_kind_of(Chef::Provider::Ifconfig)
-      expect(@resource.provider_for_action(:add)).not_to be_a_kind_of(Chef::Provider::Ifconfig::Debian)
-      expect(@resource.provider_for_action(:add)).not_to be_a_kind_of(Chef::Provider::Ifconfig::Redhat)
+      expect(@resource.provider_for_action(:add).class).to eq(Chef::Provider::Ifconfig)
     end
   end
 
   shared_examples "being a platform based on RedHat" do |platform, version|
     before do
+      @node.automatic_attrs[:os] = 'linux'
+      @node.automatic_attrs[:platform_family] = 'rhel'
       @node.automatic_attrs[:platform] = platform
       @node.automatic_attrs[:platform_version] = version
     end
@@ -73,6 +75,8 @@ describe Chef::Resource::Ifconfig do
 
   shared_examples "being a platform based on a recent Debian" do |platform, version|
     before do
+      @node.automatic_attrs[:os] = 'linux'
+      @node.automatic_attrs[:platform_family] = 'debian'
       @node.automatic_attrs[:platform] = platform
       @node.automatic_attrs[:platform_version] = version
     end
@@ -87,7 +91,7 @@ describe Chef::Resource::Ifconfig do
   end
 
   describe "when it is an old Debian platform" do
-    it_should_behave_like "being a platform using the default ifconfig provider", "debian", "6.0"
+    it_should_behave_like "being a platform based on an old Debian", "debian", "6.0"
   end
 
   describe "when it is a new Debian platform" do
@@ -95,7 +99,7 @@ describe Chef::Resource::Ifconfig do
   end
 
   describe "when it is an old Ubuntu platform" do
-    it_should_behave_like "being a platform using the default ifconfig provider", "ubuntu", "11.04"
+    it_should_behave_like "being a platform based on an old Debian", "ubuntu", "11.04"
   end
 
   describe "when it is a new Ubuntu platform" do
diff --git a/spec/unit/knife/client_list_spec.rb b/spec/unit/resource/ksh_spec.rb
similarity index 53%
copy from spec/unit/knife/client_list_spec.rb
copy to spec/unit/resource/ksh_spec.rb
index eff01da..04bd814 100644
--- a/spec/unit/knife/client_list_spec.rb
+++ b/spec/unit/resource/ksh_spec.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
-# Copyright:: Copyright (c) 2011 Thomas Bishop
+# Author:: Nolan Davidson (<nolan.davidson at gmail.com>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,17 +18,23 @@
 
 require 'spec_helper'
 
-describe Chef::Knife::ClientList do
+describe Chef::Resource::Ksh do
+
   before(:each) do
-    @knife = Chef::Knife::ClientList.new
-    @knife.name_args = [ 'adam' ]
+    @resource = Chef::Resource::Ksh.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::Ksh" do
+    expect(@resource).to be_a_kind_of(Chef::Resource)
+    expect(@resource).to be_a_kind_of(Chef::Resource::Ksh)
   end
 
-  describe 'run' do
-    it 'should list the clients' do
-      expect(Chef::ApiClient).to receive(:list)
-      expect(@knife).to receive(:format_list_for_display)
-      @knife.run
-    end
+  it "should have a resource name of :ksh" do
+    expect(@resource.resource_name).to eql(:ksh)
   end
+
+  it "should have an interpreter of ksh" do
+    expect(@resource.interpreter).to eql("ksh")
+  end
+
 end
diff --git a/spec/unit/resource/link_spec.rb b/spec/unit/resource/link_spec.rb
index 51221e0..0246fcd 100644
--- a/spec/unit/resource/link_spec.rb
+++ b/spec/unit/resource/link_spec.rb
@@ -36,7 +36,7 @@ describe Chef::Resource::Link do
   end
 
   it "should have a default action of 'create'" do
-    expect(@resource.action).to eql(:create)
+    expect(@resource.action).to eql([:create])
   end
 
   { :create => false, :delete => false, :blues => true }.each do |action,bad_value|
diff --git a/spec/unit/resource/mdadm_spec.rb b/spec/unit/resource/mdadm_spec.rb
index 866309e..6ca99c5 100644
--- a/spec/unit/resource/mdadm_spec.rb
+++ b/spec/unit/resource/mdadm_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::Mdadm do
   end
 
   it "should have a default action of create" do
-    expect(@resource.action).to eql(:create)
+    expect(@resource.action).to eql([:create])
   end
 
   it "should accept create, assemble, stop as actions" do
diff --git a/spec/unit/resource/mount_spec.rb b/spec/unit/resource/mount_spec.rb
index ad95c06..acce26d 100644
--- a/spec/unit/resource/mount_spec.rb
+++ b/spec/unit/resource/mount_spec.rb
@@ -38,7 +38,7 @@ describe Chef::Resource::Mount do
   end
 
   it "should have a default action of mount" do
-    expect(@resource.action).to eql(:mount)
+    expect(@resource.action).to eql([:mount])
   end
 
   it "should accept mount, umount and remount as actions" do
diff --git a/spec/unit/resource/ohai_spec.rb b/spec/unit/resource/ohai_spec.rb
index fe29755..3bc21a4 100644
--- a/spec/unit/resource/ohai_spec.rb
+++ b/spec/unit/resource/ohai_spec.rb
@@ -34,7 +34,7 @@ describe Chef::Resource::Ohai do
   end
 
   it "should have a default action of create" do
-    expect(@resource.action).to eql(:reload)
+    expect(@resource.action).to eql([:reload])
   end
 
   it "should allow you to set the plugin attribute" do
diff --git a/spec/unit/resource/powershell_spec.rb b/spec/unit/resource/powershell_script_spec.rb
similarity index 76%
rename from spec/unit/resource/powershell_spec.rb
rename to spec/unit/resource/powershell_script_spec.rb
index c263172..42fcd61 100644
--- a/spec/unit/resource/powershell_spec.rb
+++ b/spec/unit/resource/powershell_script_spec.rb
@@ -25,28 +25,33 @@ describe Chef::Resource::PowershellScript do
 
     node.default["kernel"] = Hash.new
     node.default["kernel"][:machine] = :x86_64.to_s
+    node.automatic[:os] = 'windows'
 
     run_context = Chef::RunContext.new(node, nil, nil)
 
     @resource = Chef::Resource::PowershellScript.new("powershell_unit_test", run_context)
-
   end
 
-  it "should create a new Chef::Resource::PowershellScript" do
+  it "creates a new Chef::Resource::PowershellScript" do
     expect(@resource).to be_a_kind_of(Chef::Resource::PowershellScript)
   end
 
-  it "should set convert_boolean_return to false by default" do
+  it "sets convert_boolean_return to false by default" do
     expect(@resource.convert_boolean_return).to eq(false)
   end
 
-  it "should return the value for convert_boolean_return that was set" do
+  it "returns the value for convert_boolean_return that was set" do
     @resource.convert_boolean_return true
     expect(@resource.convert_boolean_return).to eq(true)
     @resource.convert_boolean_return false
     expect(@resource.convert_boolean_return).to eq(false)
   end
 
+  it "raises an error when architecture is i386 on Windows Nano Server" do
+    allow(Chef::Platform).to receive(:windows_nano_server?).and_return(true)
+    expect{@resource.architecture(:i386)}.to raise_error(Chef::Exceptions::Win32ArchitectureIncorrect, "cannot execute script with requested architecture 'i386' on Windows Nano Server")
+  end
+
   context "when using guards" do
     let(:resource) { @resource }
     before(:each) do
@@ -61,32 +66,32 @@ describe Chef::Resource::PowershellScript do
       expect(inherited_difference).to eq([])
     end
 
-    it "should allow guard interpreter to be set to Chef::Resource::Script" do
+    it "allows guard interpreter to be set to Chef::Resource::Script" do
       resource.guard_interpreter(:script)
       allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
       resource.only_if("echo hi")
     end
 
-    it "should allow guard interpreter to be set to Chef::Resource::Bash derived from Chef::Resource::Script" do
+    it "allows guard interpreter to be set to Chef::Resource::Bash derived from Chef::Resource::Script" do
       resource.guard_interpreter(:bash)
       allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
       resource.only_if("echo hi")
     end
 
-    it "should allow guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do
+    it "allows guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do
       resource.guard_interpreter(:powershell_script)
       allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
       resource.only_if("echo hi")
     end
 
-    it "should enable convert_boolean_return by default for guards in the context of powershell_script when no guard params are specified" do
+    it "enables convert_boolean_return by default for guards in the context of powershell_script when no guard params are specified" do
       allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(true)
       allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
         {:convert_boolean_return => true, :code => "$true"}).and_return(Proc.new {})
       resource.only_if("$true")
     end
 
-    it "should enable convert_boolean_return by default for guards in non-Chef::Resource::Script derived resources when no guard params are specified" do
+    it "enables convert_boolean_return by default for guards in non-Chef::Resource::Script derived resources when no guard params are specified" do
       node = Chef::Node.new
       run_context = Chef::RunContext.new(node, nil, nil)
       file_resource = Chef::Resource::File.new('idontexist', run_context)
@@ -97,21 +102,21 @@ describe Chef::Resource::PowershellScript do
       resource.only_if("$true")
     end
 
-    it "should enable convert_boolean_return by default for guards in the context of powershell_script when guard params are specified" do
+    it "enables convert_boolean_return by default for guards in the context of powershell_script when guard params are specified" do
       guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64}
       allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
         {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {})
       resource.only_if("$true", guard_parameters)
     end
 
-    it "should pass convert_boolean_return as true if it was specified as true in a guard parameter" do
+    it "passes convert_boolean_return as true if it was specified as true in a guard parameter" do
       guard_parameters = {:cwd => '/etc/chef', :convert_boolean_return => true, :architecture => :x86_64}
       allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
         {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {})
       resource.only_if("$true", guard_parameters)
     end
 
-    it "should pass convert_boolean_return as false if it was specified as true in a guard parameter" do
+    it "passes convert_boolean_return as false if it was specified as true in a guard parameter" do
       other_guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64}
       parameters_with_boolean_disabled = other_guard_parameters.merge({:convert_boolean_return => false, :code => "$true"})
       allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
@@ -126,6 +131,6 @@ describe Chef::Resource::PowershellScript do
     let(:resource_name) { :powershell_script }
     let(:interpreter_file_name) { 'powershell.exe' }
 
-    it_should_behave_like "a Windows script resource"
+    it_behaves_like "a Windows script resource"
   end
 end
diff --git a/spec/unit/resource/registry_key_spec.rb b/spec/unit/resource/registry_key_spec.rb
index e2a864d..2d82f1a 100644
--- a/spec/unit/resource/registry_key_spec.rb
+++ b/spec/unit/resource/registry_key_spec.rb
@@ -45,7 +45,7 @@ describe Chef::Resource::RegistryKey, "initialize" do
   end
 
   it "should set action to :create" do
-    expect(@resource.action).to eql(:create)
+    expect(@resource.action).to eql([:create])
   end
 
   %w{create create_if_missing delete delete_key}.each do |action|
@@ -91,7 +91,7 @@ describe Chef::Resource::RegistryKey, "values" do
 
   it "should return checksummed data if the type is unsafe" do
     @resource.values( { :name => 'poosh', :type => :binary, :data => 255.chr * 1 })
-    expect(@resource.values).to eql([ { :name => 'poosh', :type => :binary, :data => "00594fd4f42ba43fc1ca0427a0576295" } ])
+    expect(@resource.values).to eql([ { :name => 'poosh', :type => :binary, :data => 'a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89' } ])
   end
 
   it "should throw an exception if the name field is missing" do
@@ -194,6 +194,6 @@ describe Chef::Resource::RegistryKey, "state" do
 
   it "should return scrubbed values" do
     @resource.values([ { :name => 'poosh', :type => :binary, :data => 255.chr * 1 } ])
-    expect(@resource.state).to eql( { :values => [{ :name => 'poosh', :type => :binary, :data => "00594fd4f42ba43fc1ca0427a0576295" }] } )
+    expect(@resource.state).to eql( { :values => [{ :name => 'poosh', :type => :binary, :data => 'a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89'}] } )
   end
 end
diff --git a/spec/unit/resource/remote_file_spec.rb b/spec/unit/resource/remote_file_spec.rb
index 3731d1a..0a379ff 100644
--- a/spec/unit/resource/remote_file_spec.rb
+++ b/spec/unit/resource/remote_file_spec.rb
@@ -39,6 +39,11 @@ describe Chef::Resource::RemoteFile do
     expect(Chef::Platform.find_provider(:noplatform, 'noversion', @resource)).to eq(Chef::Provider::RemoteFile)
   end
 
+  it "says its provider is RemoteFile when the source is a network share" do
+    @resource.source("\\\\fakey\\fakerton\\fake.txt")
+    expect(@resource.provider).to eq(Chef::Provider::RemoteFile)
+    expect(Chef::Platform.find_provider(:noplatform, 'noversion', @resource)).to eq(Chef::Provider::RemoteFile)
+  end
 
   describe "source" do
     it "does not have a default value for 'source'" do
@@ -50,6 +55,16 @@ describe Chef::Resource::RemoteFile do
       expect(@resource.source).to eql([ "http://opscode.com/" ])
     end
 
+    it "should accept a windows network share source" do
+      @resource.source "\\\\fakey\\fakerton\\fake.txt"
+      expect(@resource.source).to eql([ "\\\\fakey\\fakerton\\fake.txt" ])
+    end
+
+    it 'should accept file URIs with spaces' do
+      @resource.source("file:///C:/foo bar")
+      expect(@resource.source).to eql(["file:///C:/foo bar"])
+    end
+
     it "should accept a delayed evalutator (string) for the remote file source" do
       @resource.source Chef::DelayedEvaluator.new {"http://opscode.com/"}
       expect(@resource.source).to eql([ "http://opscode.com/" ])
diff --git a/spec/unit/resource/resource_notification_spec.rb b/spec/unit/resource/resource_notification_spec.rb
index 7f6b124..024b6f9 100644
--- a/spec/unit/resource/resource_notification_spec.rb
+++ b/spec/unit/resource/resource_notification_spec.rb
@@ -19,149 +19,148 @@ require 'spec_helper'
 require 'chef/resource/resource_notification'
 
 describe Chef::Resource::Notification do
-  before do
-    @notification = Chef::Resource::Notification.new(:service_apache, :restart, :template_httpd_conf)
-  end
+
+  let(:notification) { Chef::Resource::Notification.new(:service_apache, :restart, :template_httpd_conf) }
 
   it "has a resource to be notified" do
-    expect(@notification.resource).to eq(:service_apache)
+    expect(notification.resource).to eq(:service_apache)
   end
 
   it "has an action to take on the service" do
-    expect(@notification.action).to eq(:restart)
+    expect(notification.action).to eq(:restart)
   end
 
   it "has a notifying resource" do
-    expect(@notification.notifying_resource).to eq(:template_httpd_conf)
+    expect(notification.notifying_resource).to eq(:template_httpd_conf)
   end
 
   it "is a duplicate of another notification with the same target resource and action" do
     other = Chef::Resource::Notification.new(:service_apache, :restart, :sync_web_app_code)
-    expect(@notification.duplicates?(other)).to be_truthy
+    expect(notification.duplicates?(other)).to be_truthy
   end
 
   it "is not a duplicate of another notification if the actions differ" do
     other = Chef::Resource::Notification.new(:service_apache, :enable, :install_apache)
-    expect(@notification.duplicates?(other)).to be_falsey
+    expect(notification.duplicates?(other)).to be_falsey
   end
 
   it "is not a duplicate of another notification if the target resources differ" do
     other = Chef::Resource::Notification.new(:service_sshd, :restart, :template_httpd_conf)
-    expect(@notification.duplicates?(other)).to be_falsey
+    expect(notification.duplicates?(other)).to be_falsey
   end
 
   it "raises an ArgumentError if you try to check a non-ducktype object for duplication" do
-    expect {@notification.duplicates?(:not_a_notification)}.to raise_error(ArgumentError)
+    expect {notification.duplicates?(:not_a_notification)}.to raise_error(ArgumentError)
   end
 
   it "takes no action to resolve a resource reference that doesn't need to be resolved" do
     @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
-    @notification.resource = @keyboard_cat
+    notification.resource = @keyboard_cat
     @long_cat = Chef::Resource::Cat.new("long_cat")
-    @notification.notifying_resource = @long_cat
+    notification.notifying_resource = @long_cat
     @resource_collection = Chef::ResourceCollection.new
     # would raise an error since the resource is not in the collection
-    @notification.resolve_resource_reference(@resource_collection)
-    expect(@notification.resource).to eq(@keyboard_cat)
+    notification.resolve_resource_reference(@resource_collection)
+    expect(notification.resource).to eq(@keyboard_cat)
   end
 
   it "resolves a lazy reference to a resource" do
-    @notification.resource = {:cat => "keyboard_cat"}
+    notification.resource = {:cat => "keyboard_cat"}
     @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
     @resource_collection = Chef::ResourceCollection.new
     @resource_collection << @keyboard_cat
     @long_cat = Chef::Resource::Cat.new("long_cat")
-    @notification.notifying_resource = @long_cat
-    @notification.resolve_resource_reference(@resource_collection)
-    expect(@notification.resource).to eq(@keyboard_cat)
+    notification.notifying_resource = @long_cat
+    notification.resolve_resource_reference(@resource_collection)
+    expect(notification.resource).to eq(@keyboard_cat)
   end
 
   it "resolves a lazy reference to its notifying resource" do
     @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
-    @notification.resource = @keyboard_cat
-    @notification.notifying_resource = {:cat => "long_cat"}
+    notification.resource = @keyboard_cat
+    notification.notifying_resource = {:cat => "long_cat"}
     @long_cat = Chef::Resource::Cat.new("long_cat")
     @resource_collection = Chef::ResourceCollection.new
     @resource_collection << @long_cat
-    @notification.resolve_resource_reference(@resource_collection)
-    expect(@notification.notifying_resource).to eq(@long_cat)
+    notification.resolve_resource_reference(@resource_collection)
+    expect(notification.notifying_resource).to eq(@long_cat)
   end
 
   it "resolves lazy references to both its resource and its notifying resource" do
-    @notification.resource = {:cat => "keyboard_cat"}
+    notification.resource = {:cat => "keyboard_cat"}
     @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
     @resource_collection = Chef::ResourceCollection.new
     @resource_collection << @keyboard_cat
-    @notification.notifying_resource = {:cat => "long_cat"}
+    notification.notifying_resource = {:cat => "long_cat"}
     @long_cat = Chef::Resource::Cat.new("long_cat")
     @resource_collection << @long_cat
-    @notification.resolve_resource_reference(@resource_collection)
-    expect(@notification.resource).to eq(@keyboard_cat)
-    expect(@notification.notifying_resource).to eq(@long_cat)
+    notification.resolve_resource_reference(@resource_collection)
+    expect(notification.resource).to eq(@keyboard_cat)
+    expect(notification.notifying_resource).to eq(@long_cat)
   end
 
   it "raises a RuntimeError if you try to reference multiple resources" do
-    @notification.resource = {:cat => ["keyboard_cat", "cheez_cat"]}
+    notification.resource = {:cat => ["keyboard_cat", "cheez_cat"]}
     @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
     @cheez_cat = Chef::Resource::Cat.new("cheez_cat")
     @resource_collection = Chef::ResourceCollection.new
     @resource_collection << @keyboard_cat
     @resource_collection << @cheez_cat
     @long_cat = Chef::Resource::Cat.new("long_cat")
-    @notification.notifying_resource = @long_cat
-    expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError)
+    notification.notifying_resource = @long_cat
+    expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError)
   end
 
   it "raises a RuntimeError if you try to reference multiple notifying resources" do
-    @notification.notifying_resource = {:cat => ["long_cat", "cheez_cat"]}
+    notification.notifying_resource = {:cat => ["long_cat", "cheez_cat"]}
     @long_cat = Chef::Resource::Cat.new("long_cat")
     @cheez_cat = Chef::Resource::Cat.new("cheez_cat")
     @resource_collection = Chef::ResourceCollection.new
     @resource_collection << @long_cat
     @resource_collection << @cheez_cat
     @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
-    @notification.resource = @keyboard_cat
-    expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError)
+    notification.resource = @keyboard_cat
+    expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError)
   end
 
   it "raises a RuntimeError if it can't find a resource in the resource collection when resolving a lazy reference" do
-    @notification.resource = {:cat => "keyboard_cat"}
+    notification.resource = {:cat => "keyboard_cat"}
     @cheez_cat = Chef::Resource::Cat.new("cheez_cat")
     @resource_collection = Chef::ResourceCollection.new
     @resource_collection << @cheez_cat
     @long_cat = Chef::Resource::Cat.new("long_cat")
-    @notification.notifying_resource = @long_cat
-    expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError)
+    notification.notifying_resource = @long_cat
+    expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError)
   end
 
   it "raises a RuntimeError if it can't find a notifying resource in the resource collection when resolving a lazy reference" do
-    @notification.notifying_resource = {:cat => "long_cat"}
+    notification.notifying_resource = {:cat => "long_cat"}
     @cheez_cat = Chef::Resource::Cat.new("cheez_cat")
     @resource_collection = Chef::ResourceCollection.new
     @resource_collection << @cheez_cat
     @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
-    @notification.resource = @keyboard_cat
-    expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError)
+    notification.resource = @keyboard_cat
+    expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError)
   end
 
   it "raises an ArgumentError if improper syntax is used in the lazy reference to its resource" do
-    @notification.resource = "cat => keyboard_cat"
+    notification.resource = "cat => keyboard_cat"
     @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
     @resource_collection = Chef::ResourceCollection.new
     @resource_collection << @keyboard_cat
     @long_cat = Chef::Resource::Cat.new("long_cat")
-    @notification.notifying_resource = @long_cat
-    expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(ArgumentError)
+    notification.notifying_resource = @long_cat
+    expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(ArgumentError)
   end
 
   it "raises an ArgumentError if improper syntax is used in the lazy reference to its notifying resource" do
-    @notification.notifying_resource = "cat => long_cat"
+    notification.notifying_resource = "cat => long_cat"
     @long_cat = Chef::Resource::Cat.new("long_cat")
     @resource_collection = Chef::ResourceCollection.new
     @resource_collection << @long_cat
     @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
-    @notification.resource = @keyboard_cat
-    expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(ArgumentError)
+    notification.resource = @keyboard_cat
+    expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(ArgumentError)
   end
 
   # Create test to resolve lazy references to both notifying resource and dest. resource
diff --git a/spec/unit/resource/ruby_block_spec.rb b/spec/unit/resource/ruby_block_spec.rb
index 9f19fec..8664564 100644
--- a/spec/unit/resource/ruby_block_spec.rb
+++ b/spec/unit/resource/ruby_block_spec.rb
@@ -30,8 +30,8 @@ describe Chef::Resource::RubyBlock do
     expect(@resource).to be_a_kind_of(Chef::Resource::RubyBlock)
   end
 
-  it "should have a default action of 'create'" do
-    expect(@resource.action).to eql("run")
+  it "should have a default action of 'run'" do
+    expect(@resource.action).to eql([:run])
   end
 
   it "should have a resource name of :ruby_block" do
diff --git a/spec/unit/resource/service_spec.rb b/spec/unit/resource/service_spec.rb
index eb6f444..b9e3757 100644
--- a/spec/unit/resource/service_spec.rb
+++ b/spec/unit/resource/service_spec.rb
@@ -1,7 +1,7 @@
 #
 # Author:: AJ Christensen (<aj at hjksolutions.com>)
 # Author:: Tyler Cloke (<tyler at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -139,14 +139,14 @@ describe Chef::Resource::Service do
       expect { @resource.send(attrib, "poop") }.to raise_error(ArgumentError)
     end
 
-    it "should default all the feature support to false" do
-      support_hash = { :status => false, :restart => false, :reload=> false }
+    it "should default all the feature support to nil" do
+      support_hash = { :status => nil, :restart => nil, :reload=> nil }
       expect(@resource.supports).to eq(support_hash)
     end
 
     it "should allow you to set what features this resource supports as a array" do
       support_array = [ :status, :restart ]
-      support_hash = { :status => true, :restart => true, :reload => false }
+      support_hash = { :status => true, :restart => true, :reload => nil }
       @resource.supports(support_array)
       expect(@resource.supports).to eq(support_hash)
     end
diff --git a/spec/unit/resource/subversion_spec.rb b/spec/unit/resource/subversion_spec.rb
index 5cd5d0d..aa4d1ed 100644
--- a/spec/unit/resource/subversion_spec.rb
+++ b/spec/unit/resource/subversion_spec.rb
@@ -54,6 +54,10 @@ describe Chef::Resource::Subversion do
     expect(@svn.svn_arguments).to eq('--no-auth-cache')
   end
 
+  it "sets svn binary to nil by default" do
+    expect(@svn.svn_binary).to be_nil
+  end
+
   it "resets svn arguments to nil when given false in the setter" do
     @svn.svn_arguments(false)
     expect(@svn.svn_arguments).to be_nil
diff --git a/spec/unit/resource/template_spec.rb b/spec/unit/resource/template_spec.rb
index df5ca94..2fd951b 100644
--- a/spec/unit/resource/template_spec.rb
+++ b/spec/unit/resource/template_spec.rb
@@ -98,7 +98,7 @@ describe Chef::Resource::Template do
 
     context "on windows", :windows_only do
       # according to Chef::Resource::File, windows state attributes are rights + deny_rights
-      pending "it describes its state"
+      skip "it describes its state"
     end
 
     it "returns the file path as its identity" do
diff --git a/spec/unit/resource/timestamped_deploy_spec.rb b/spec/unit/resource/timestamped_deploy_spec.rb
index eca6c57..4ebfdaf 100644
--- a/spec/unit/resource/timestamped_deploy_spec.rb
+++ b/spec/unit/resource/timestamped_deploy_spec.rb
@@ -23,11 +23,10 @@ describe Chef::Resource::TimestampedDeploy, "initialize" do
   static_provider_resolution(
     resource: Chef::Resource::TimestampedDeploy,
     provider: Chef::Provider::Deploy::Timestamped,
-    name: :deploy,
+    name: :timestamped_deploy,
     action: :deploy,
     os: 'linux',
     platform_family: 'rhel',
   )
 
 end
-
diff --git a/spec/unit/resource/user_spec.rb b/spec/unit/resource/user_spec.rb
index f05de94..3bf7e61 100644
--- a/spec/unit/resource/user_spec.rb
+++ b/spec/unit/resource/user_spec.rb
@@ -43,7 +43,7 @@ describe Chef::Resource::User, "initialize" do
   end
 
   it "should set action to :create" do
-    expect(@resource.action).to eql(:create)
+    expect(@resource.action).to eql([:create])
   end
 
   it "should set supports[:manage_home] to false" do
diff --git a/spec/unit/resource/windows_package_spec.rb b/spec/unit/resource/windows_package_spec.rb
index 1e02f24..6aa5d35 100644
--- a/spec/unit/resource/windows_package_spec.rb
+++ b/spec/unit/resource/windows_package_spec.rb
@@ -63,9 +63,9 @@ describe Chef::Resource::WindowsPackage, "initialize" do
   end
 
   it "coverts a source to an absolute path" do
-    allow(::File).to receive(:absolute_path).and_return("c:\\Files\\frost.msi")
+    allow(::File).to receive(:absolute_path).and_return("c:\\files\\frost.msi")
     resource.source("frost.msi")
-    expect(resource.source).to eql "c:\\Files\\frost.msi"
+    expect(resource.source).to eql "c:\\files\\frost.msi"
   end
 
   it "converts slashes to backslashes in the source path" do
@@ -78,4 +78,18 @@ describe Chef::Resource::WindowsPackage, "initialize" do
     # it's a little late to stub out File.absolute_path
     expect(resource.source).to include("solitaire.msi")
   end
+
+  it "supports the checksum attribute" do
+    resource.checksum('somechecksum')
+    expect(resource.checksum).to eq('somechecksum')
+  end
+
+  context 'when a URL is used' do
+    let(:resource_source) { 'https://foo.bar/solitare.msi' }
+    let(:resource) { Chef::Resource::WindowsPackage.new(resource_source) }
+
+    it "should return the source unmodified" do
+      expect(resource.source).to eq(resource_source)
+    end
+  end
 end
diff --git a/spec/unit/resource/yum_package_spec.rb b/spec/unit/resource/yum_package_spec.rb
index e01b87c..f24f1e3 100644
--- a/spec/unit/resource/yum_package_spec.rb
+++ b/spec/unit/resource/yum_package_spec.rb
@@ -1,6 +1,6 @@
 #
 # Author:: AJ Christensen (<aj at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -78,3 +78,12 @@ describe Chef::Resource::YumPackage, "allow_downgrade" do
     expect { @resource.allow_downgrade "monkey" }.to raise_error(ArgumentError)
   end
 end
+
+describe Chef::Resource::YumPackage, "yum_binary" do
+  let(:resource) { Chef::Resource::YumPackage.new("foo") }
+
+  it "should allow you to specify the yum_binary" do
+    resource.yum_binary "/usr/bin/yum-something"
+    expect(resource.yum_binary).to eql("/usr/bin/yum-something")
+  end
+end
diff --git a/spec/unit/resource_collection_spec.rb b/spec/unit/resource_collection_spec.rb
index b43b012..d52e7e2 100644
--- a/spec/unit/resource_collection_spec.rb
+++ b/spec/unit/resource_collection_spec.rb
@@ -252,7 +252,7 @@ describe Chef::ResourceCollection do
       expect(json).to match(/instance_vars/)
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) { rc }
     end
   end
diff --git a/spec/unit/resource_reporter_spec.rb b/spec/unit/resource_reporter_spec.rb
index 4f3a085..f2c0b8f 100644
--- a/spec/unit/resource_reporter_spec.rb
+++ b/spec/unit/resource_reporter_spec.rb
@@ -50,6 +50,9 @@ describe Chef::ResourceReporter do
     @events = Chef::EventDispatch::Dispatcher.new
     @run_context = Chef::RunContext.new(@node, {}, @events)
     @run_status = Chef::RunStatus.new(@node, @events)
+    @run_list = Chef::RunList.new
+    @run_list << 'recipe[lobster]' << 'role[rage]' << 'recipe[fist]'
+    @expansion = Chef::RunList::RunListExpansion.new("_default", @run_list.run_list_items)
     @run_id = @run_status.run_id
     allow(Time).to receive(:now).and_return(@start_time, @end_time)
   end
@@ -424,6 +427,10 @@ describe Chef::ResourceReporter do
         expect(@report["run_list"]).to eq(Chef::JSONCompat.to_json(@run_status.node.run_list))
       end
 
+      it "includes the expanded_run_list" do
+        expect(@report).to have_key("expanded_run_list")
+      end
+
       it "includes the end_time" do
         expect(@report).to have_key("end_time")
         expect(@report["end_time"]).to eq(@run_status.end_time.to_s)
diff --git a/spec/unit/resource_resolver_spec.rb b/spec/unit/resource_resolver_spec.rb
new file mode 100644
index 0000000..b3bda9d
--- /dev/null
+++ b/spec/unit/resource_resolver_spec.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Ranjib Dey
+# Copyright:: Copyright (c) 2015 Ranjib Dey <ranjib at linux.com>.
+# 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 'spec_helper'
+require 'chef/resource_resolver'
+
+
+describe Chef::ResourceResolver do
+  it '#resolve' do
+    expect(described_class.resolve(:execute)).to eq(Chef::Resource::Execute)
+  end
+
+  it '#list' do
+    expect(described_class.list(:package)).to_not be_empty
+  end
+
+  context 'instance methods' do
+    let(:resolver) do
+      described_class.new(Chef::Node.new, 'execute')
+    end
+
+    it '#resolve' do
+      expect(resolver.resolve).to eq Chef::Resource::Execute
+    end
+
+    it '#list' do
+      expect(resolver.list).to eq [ Chef::Resource::Execute ]
+    end
+
+    it '#provided_by? returns true when resource class is in the list' do
+      expect(resolver.provided_by?(Chef::Resource::Execute)).to be_truthy
+    end
+
+    it '#provided_by? returns false when resource class is not in the list' do
+      expect(resolver.provided_by?(Chef::Resource::File)).to be_falsey
+    end
+  end
+end
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index 6b2d6c8..5995194 100644
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -21,18 +21,25 @@
 
 require 'spec_helper'
 
-class ResourceTestHarness < Chef::Resource
-  provider_base Chef::Provider::Package
-end
-
 describe Chef::Resource do
-  before(:each) do
-    @cookbook_repo_path =  File.join(CHEF_SPEC_DATA, 'cookbooks')
-    @cookbook_collection = Chef::CookbookCollection.new(Chef::CookbookLoader.new(@cookbook_repo_path))
-    @node = Chef::Node.new
-    @events = Chef::EventDispatch::Dispatcher.new
-    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
-    @resource = Chef::Resource.new("funk", @run_context)
+  let(:cookbook_repo_path) { File.join(CHEF_SPEC_DATA, 'cookbooks') }
+  let(:cookbook_collection) { Chef::CookbookCollection.new(Chef::CookbookLoader.new(cookbook_repo_path)) }
+  let(:node) { Chef::Node.new }
+  let(:events) { Chef::EventDispatch::Dispatcher.new }
+  let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) }
+  let(:resource) { resource_class.new("funk", run_context) }
+  let(:resource_class) { Chef::Resource }
+
+  it "should mixin shell_out" do
+    expect(resource.respond_to?(:shell_out)).to be true
+  end
+
+  it "should mixin shell_out!" do
+    expect(resource.respond_to?(:shell_out!)).to be true
+  end
+
+  it "should mixin shell_out_with_systems_locale" do
+    expect(resource.respond_to?(:shell_out_with_systems_locale)).to be true
   end
 
   describe "when inherited" do
@@ -51,8 +58,8 @@ describe Chef::Resource do
   end
 
   describe "when declaring the identity attribute" do
-    it "has no identity attribute by default" do
-      expect(Chef::Resource.identity_attr).to be_nil
+    it "has :name as identity attribute by default" do
+      expect(Chef::Resource.identity_attr).to eq(:name)
     end
 
     it "sets an identity attribute" do
@@ -78,30 +85,29 @@ describe Chef::Resource do
   end
 
   describe "when no identity attribute has been declared" do
-    before do
-      @resource_sans_id = Chef::Resource.new("my-name")
-    end
+    let(:resource_sans_id) { Chef::Resource.new("my-name") }
 
     # Would rather force identity attributes to be set for everything,
     # but that's not plausible for back compat reasons.
     it "uses the name as the identity" do
-      expect(@resource_sans_id.identity).to eq("my-name")
+      expect(resource_sans_id.identity).to eq("my-name")
     end
   end
 
   describe "when an identity attribute has been declared" do
-    before do
-      @file_resource_class = Class.new(Chef::Resource) do
+    let(:file_resource) {
+      file_resource_class = Class.new(Chef::Resource) do
         identity_attr :path
         attr_accessor :path
       end
 
-      @file_resource = @file_resource_class.new("identity-attr-test")
-      @file_resource.path = "/tmp/foo.txt"
-    end
+      file_resource = file_resource_class.new("identity-attr-test")
+      file_resource.path = "/tmp/foo.txt"
+      file_resource
+    }
 
     it "gives the value of its identity attribute" do
-      expect(@file_resource.identity).to eq("/tmp/foo.txt")
+      expect(file_resource.identity).to eq("/tmp/foo.txt")
     end
   end
 
@@ -134,8 +140,8 @@ describe Chef::Resource do
   end
 
   describe "when a set of state attributes has been declared" do
-    before do
-      @file_resource_class = Class.new(Chef::Resource) do
+    let(:file_resource) {
+      file_resource_class = Class.new(Chef::Resource) do
 
         state_attrs :checksum, :owner, :group, :mode
 
@@ -145,15 +151,16 @@ describe Chef::Resource do
         attr_accessor :mode
       end
 
-      @file_resource = @file_resource_class.new("describe-state-test")
-      @file_resource.checksum = "abc123"
-      @file_resource.owner = "root"
-      @file_resource.group = "wheel"
-      @file_resource.mode = "0644"
-    end
+      file_resource = file_resource_class.new("describe-state-test")
+      file_resource.checksum = "abc123"
+      file_resource.owner = "root"
+      file_resource.group = "wheel"
+      file_resource.mode = "0644"
+      file_resource
+    }
 
     it "describes its state" do
-      resource_state = @file_resource.state
+      resource_state = file_resource.state
       expect(resource_state.keys).to match_array([:checksum, :owner, :group, :mode])
       expect(resource_state[:checksum]).to eq("abc123")
       expect(resource_state[:owner]).to eq("root")
@@ -163,157 +170,160 @@ describe Chef::Resource do
   end
 
   describe "load_from" do
+    let(:prior_resource) {
+      prior_resource = Chef::Resource.new("funk")
+      prior_resource.supports(:funky => true)
+      prior_resource.source_line
+      prior_resource.allowed_actions << :funkytown
+      prior_resource.action(:funkytown)
+      prior_resource
+    }
     before(:each) do
-      @prior_resource = Chef::Resource.new("funk")
-      @prior_resource.supports(:funky => true)
-      @prior_resource.source_line
-      @prior_resource.allowed_actions << :funkytown
-      @prior_resource.action(:funkytown)
-      @resource.allowed_actions << :funkytown
-      @run_context.resource_collection << @prior_resource
+      resource.allowed_actions << :funkytown
+      run_context.resource_collection << prior_resource
     end
 
     it "should load the attributes of a prior resource" do
-      @resource.load_from(@prior_resource)
-      expect(@resource.supports).to eq({ :funky => true })
+      resource.load_from(prior_resource)
+      expect(resource.supports).to eq({ :funky => true })
     end
 
     it "should not inherit the action from the prior resource" do
-      @resource.load_from(@prior_resource)
-      expect(@resource.action).not_to eq(@prior_resource.action)
+      resource.load_from(prior_resource)
+      expect(resource.action).not_to eq(prior_resource.action)
     end
   end
 
   describe "name" do
     it "should have a name" do
-      expect(@resource.name).to eql("funk")
+      expect(resource.name).to eql("funk")
     end
 
     it "should let you set a new name" do
-      @resource.name "monkey"
-      expect(@resource.name).to eql("monkey")
+      resource.name "monkey"
+      expect(resource.name).to eql("monkey")
     end
 
     it "coerces arrays to names" do
-      expect(@resource.name ['a', 'b']).to eql('a, b')
+      expect(resource.name ['a', 'b']).to eql('a, b')
     end
 
     it "should coerce objects to a string" do
-      expect(@resource.name Object.new).to be_a(String)
+      expect(resource.name Object.new).to be_a(String)
     end
   end
 
   describe "noop" do
     it "should accept true or false for noop" do
-      expect { @resource.noop true }.not_to raise_error
-      expect { @resource.noop false }.not_to raise_error
-      expect { @resource.noop "eat it" }.to raise_error(ArgumentError)
+      expect { resource.noop true }.not_to raise_error
+      expect { resource.noop false }.not_to raise_error
+      expect { resource.noop "eat it" }.to raise_error(ArgumentError)
     end
   end
 
   describe "notifies" do
     it "should make notified resources appear in the actions hash" do
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
-      @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "coffee")
-      expect(@resource.delayed_notifications.detect{|e| e.resource.name == "coffee" && e.action == :reload}).not_to be_nil
+      run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee")
+      expect(resource.delayed_notifications.detect{|e| e.resource.name == "coffee" && e.action == :reload}).not_to be_nil
     end
 
     it "should make notified resources be capable of acting immediately" do
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
-      @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "coffee"), :immediate
-      expect(@resource.immediate_notifications.detect{|e| e.resource.name == "coffee" && e.action == :reload}).not_to be_nil
+      run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee"), :immediate
+      expect(resource.immediate_notifications.detect{|e| e.resource.name == "coffee" && e.action == :reload}).not_to be_nil
     end
 
     it "should raise an exception if told to act in other than :delay or :immediate(ly)" do
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
       expect {
-        @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "coffee"), :someday
+        resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee"), :someday
       }.to raise_error(ArgumentError)
     end
 
     it "should allow multiple notified resources appear in the actions hash" do
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
-      @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "coffee")
-      expect(@resource.delayed_notifications.detect{|e| e.resource.name == "coffee" && e.action == :reload}).not_to be_nil
+      run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee")
+      expect(resource.delayed_notifications.detect{|e| e.resource.name == "coffee" && e.action == :reload}).not_to be_nil
 
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new("beans")
-      @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "beans")
-      expect(@resource.delayed_notifications.detect{|e| e.resource.name == "beans" && e.action == :reload}).not_to be_nil
+      run_context.resource_collection << Chef::Resource::ZenMaster.new("beans")
+      resource.notifies :reload, run_context.resource_collection.find(:zen_master => "beans")
+      expect(resource.delayed_notifications.detect{|e| e.resource.name == "beans" && e.action == :reload}).not_to be_nil
     end
 
     it "creates a notification for a resource that is not yet in the resource collection" do
-      @resource.notifies(:restart, :service => 'apache')
-      expected_notification = Chef::Resource::Notification.new({:service => "apache"}, :restart, @resource)
-      expect(@resource.delayed_notifications).to include(expected_notification)
+      resource.notifies(:restart, :service => 'apache')
+      expected_notification = Chef::Resource::Notification.new({:service => "apache"}, :restart, resource)
+      expect(resource.delayed_notifications).to include(expected_notification)
     end
 
     it "notifies another resource immediately" do
-      @resource.notifies_immediately(:restart, :service => 'apache')
-      expected_notification = Chef::Resource::Notification.new({:service => "apache"}, :restart, @resource)
-      expect(@resource.immediate_notifications).to include(expected_notification)
+      resource.notifies_immediately(:restart, :service => 'apache')
+      expected_notification = Chef::Resource::Notification.new({:service => "apache"}, :restart, resource)
+      expect(resource.immediate_notifications).to include(expected_notification)
     end
 
     it "notifies a resource to take action at the end of the chef run" do
-      @resource.notifies_delayed(:restart, :service => "apache")
-      expected_notification = Chef::Resource::Notification.new({:service => "apache"}, :restart, @resource)
-      expect(@resource.delayed_notifications).to include(expected_notification)
+      resource.notifies_delayed(:restart, :service => "apache")
+      expected_notification = Chef::Resource::Notification.new({:service => "apache"}, :restart, resource)
+      expect(resource.delayed_notifications).to include(expected_notification)
     end
 
     it "notifies a resource with an array for its name via its prettified string name" do
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new(["coffee", "tea"])
-      @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "coffee, tea")
-      expect(@resource.delayed_notifications.detect{|e| e.resource.name == "coffee, tea" && e.action == :reload}).not_to be_nil
+      run_context.resource_collection << Chef::Resource::ZenMaster.new(["coffee", "tea"])
+      resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee, tea")
+      expect(resource.delayed_notifications.detect{|e| e.resource.name == "coffee, tea" && e.action == :reload}).not_to be_nil
     end
   end
 
   describe "subscribes" do
     it "should make resources appear in the actions hash of subscribed nodes" do
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
-      zr = @run_context.resource_collection.find(:zen_master => "coffee")
-      @resource.subscribes :reload, zr
+      run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      zr = run_context.resource_collection.find(:zen_master => "coffee")
+      resource.subscribes :reload, zr
       expect(zr.delayed_notifications.detect{|e| e.resource.name == "funk" && e.action == :reload}).not_to be_nil
     end
 
     it "should make resources appear in the actions hash of subscribed nodes" do
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
-      zr = @run_context.resource_collection.find(:zen_master => "coffee")
-      @resource.subscribes :reload, zr
-      expect(zr.delayed_notifications.detect{|e| e.resource.name == @resource.name && e.action == :reload}).not_to be_nil
+      run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      zr = run_context.resource_collection.find(:zen_master => "coffee")
+      resource.subscribes :reload, zr
+      expect(zr.delayed_notifications.detect{|e| e.resource.name == resource.name && e.action == :reload}).not_to be_nil
 
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new("bean")
-      zrb = @run_context.resource_collection.find(:zen_master => "bean")
+      run_context.resource_collection << Chef::Resource::ZenMaster.new("bean")
+      zrb = run_context.resource_collection.find(:zen_master => "bean")
       zrb.subscribes :reload, zr
-      expect(zr.delayed_notifications.detect{|e| e.resource.name == @resource.name && e.action == :reload}).not_to be_nil
+      expect(zr.delayed_notifications.detect{|e| e.resource.name == resource.name && e.action == :reload}).not_to be_nil
     end
 
     it "should make subscribed resources be capable of acting immediately" do
-      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
-      zr = @run_context.resource_collection.find(:zen_master => "coffee")
-      @resource.subscribes :reload, zr, :immediately
-      expect(zr.immediate_notifications.detect{|e| e.resource.name == @resource.name && e.action == :reload}).not_to be_nil
+      run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      zr = run_context.resource_collection.find(:zen_master => "coffee")
+      resource.subscribes :reload, zr, :immediately
+      expect(zr.immediate_notifications.detect{|e| e.resource.name == resource.name && e.action == :reload}).not_to be_nil
     end
   end
 
   describe "defined_at" do
     it "should correctly parse source_line on unix-like operating systems" do
-      @resource.source_line = "/some/path/to/file.rb:80:in `wombat_tears'"
-      expect(@resource.defined_at).to eq("/some/path/to/file.rb line 80")
+      resource.source_line = "/some/path/to/file.rb:80:in `wombat_tears'"
+      expect(resource.defined_at).to eq("/some/path/to/file.rb line 80")
     end
 
     it "should correctly parse source_line on Windows" do
-      @resource.source_line = "C:/some/path/to/file.rb:80 in 1`wombat_tears'"
-      expect(@resource.defined_at).to eq("C:/some/path/to/file.rb line 80")
+      resource.source_line = "C:/some/path/to/file.rb:80 in 1`wombat_tears'"
+      expect(resource.defined_at).to eq("C:/some/path/to/file.rb line 80")
     end
 
     it "should include the cookbook and recipe when it knows it" do
-      @resource.source_line = "/some/path/to/file.rb:80:in `wombat_tears'"
-      @resource.recipe_name = "wombats"
-      @resource.cookbook_name = "animals"
-      expect(@resource.defined_at).to eq("animals::wombats line 80")
+      resource.source_line = "/some/path/to/file.rb:80:in `wombat_tears'"
+      resource.recipe_name = "wombats"
+      resource.cookbook_name = "animals"
+      expect(resource.defined_at).to eq("animals::wombats line 80")
     end
 
     it "should recognize dynamically defined resources" do
-      expect(@resource.defined_at).to eq("dynamically defined")
+      expect(resource.defined_at).to eq("dynamically defined")
     end
   end
 
@@ -324,6 +334,86 @@ describe Chef::Resource do
     end
   end
 
+  describe "self.resource_name" do
+    context "When resource_name is not set" do
+      it "and there are no provides lines, resource_name is nil" do
+        c = Class.new(Chef::Resource) do
+        end
+
+        r = c.new('hi')
+        r.declared_type = :d
+        expect(c.resource_name).to be_nil
+        expect(r.resource_name).to be_nil
+        expect(r.declared_type).to eq :d
+      end
+
+      it "and there are no provides lines, resource_name is used" do
+        c = Class.new(Chef::Resource) do
+          def initialize(*args, &block)
+            @resource_name = :blah
+            super
+          end
+        end
+
+        r = c.new('hi')
+        r.declared_type = :d
+        expect(c.resource_name).to be_nil
+        expect(r.resource_name).to eq :blah
+        expect(r.declared_type).to eq :d
+      end
+
+      it "and the resource class gets a late-bound name, resource_name is nil" do
+        c = Class.new(Chef::Resource) do
+          def self.name
+            "ResourceSpecNameTest"
+          end
+        end
+
+        r = c.new('hi')
+        r.declared_type = :d
+        expect(c.resource_name).to be_nil
+        expect(r.resource_name).to be_nil
+        expect(r.declared_type).to eq :d
+      end
+    end
+
+    it "resource_name without provides is honored" do
+      c = Class.new(Chef::Resource) do
+        resource_name 'blah'
+      end
+
+      r = c.new('hi')
+      r.declared_type = :d
+      expect(c.resource_name).to eq :blah
+      expect(r.resource_name).to eq :blah
+      expect(r.declared_type).to eq :d
+    end
+    it "setting class.resource_name with 'resource_name = blah' overrides declared_type" do
+      c = Class.new(Chef::Resource) do
+        provides :self_resource_name_test_2
+      end
+      c.resource_name = :blah
+
+      r = c.new('hi')
+      r.declared_type = :d
+      expect(c.resource_name).to eq :blah
+      expect(r.resource_name).to eq :blah
+      expect(r.declared_type).to eq :d
+    end
+    it "setting class.resource_name with 'resource_name blah' overrides declared_type" do
+      c = Class.new(Chef::Resource) do
+        resource_name :blah
+        provides :self_resource_name_test_3
+      end
+
+      r = c.new('hi')
+      r.declared_type = :d
+      expect(c.resource_name).to eq :blah
+      expect(r.resource_name).to eq :blah
+      expect(r.declared_type).to eq :d
+    end
+  end
+
   describe "is" do
     it "should return the arguments passed with 'is'" do
       zm = Chef::Resource::ZenMaster.new("coffee")
@@ -331,26 +421,39 @@ describe Chef::Resource do
     end
 
     it "should allow arguments preceded by is to methods" do
-      @resource.noop(@resource.is(true))
-      expect(@resource.noop).to eql(true)
+      resource.noop(resource.is(true))
+      expect(resource.noop).to eql(true)
     end
   end
 
   describe "to_json" do
     it "should serialize to json" do
-      json = @resource.to_json
+      json = resource.to_json
       expect(json).to match(/json_class/)
       expect(json).to match(/instance_vars/)
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
-      let(:jsonable) { @resource }
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
+      let(:jsonable) { resource }
     end
   end
 
   describe "to_hash" do
+    context "when the resource has a property with a default" do
+      let(:resource_class) { Class.new(Chef::Resource) { property :a, default: 1 } }
+      it "should include the default in the hash" do
+        expect(resource.to_hash.keys.sort).to eq([:a, :allowed_actions, :params, :provider, :updated,
+          :updated_by_last_action, :before, :supports,
+          :noop, :ignore_failure, :name, :source_line,
+          :action, :retries, :retry_delay, :elapsed_time,
+          :default_guard_interpreter, :guard_interpreter, :sensitive].sort)
+        expect(resource.to_hash[:name]).to eq "funk"
+        expect(resource.to_hash[:a]).to eq 1
+      end
+    end
+
     it "should convert to a hash" do
-      hash = @resource.to_hash
+      hash = resource.to_hash
       expected_keys = [ :allowed_actions, :params, :provider, :updated,
         :updated_by_last_action, :before, :supports,
         :noop, :ignore_failure, :name, :source_line,
@@ -364,80 +467,83 @@ describe Chef::Resource do
 
   describe "self.json_create" do
     it "should deserialize itself from json" do
-      json = Chef::JSONCompat.to_json(@resource)
+      json = Chef::JSONCompat.to_json(resource)
       serialized_node = Chef::JSONCompat.from_json(json)
       expect(serialized_node).to be_a_kind_of(Chef::Resource)
-      expect(serialized_node.name).to eql(@resource.name)
+      expect(serialized_node.name).to eql(resource.name)
     end
   end
 
   describe "supports" do
     it "should allow you to set what features this resource supports" do
       support_hash = { :one => :two }
-      @resource.supports(support_hash)
-      expect(@resource.supports).to eql(support_hash)
+      resource.supports(support_hash)
+      expect(resource.supports).to eql(support_hash)
     end
 
     it "should return the current value of supports" do
-      expect(@resource.supports).to eq({})
+      expect(resource.supports).to eq({})
     end
   end
 
   describe "ignore_failure" do
     it "should default to throwing an error if a provider fails for a resource" do
-      expect(@resource.ignore_failure).to eq(false)
+      expect(resource.ignore_failure).to eq(false)
     end
 
     it "should allow you to set whether a provider should throw exceptions with ignore_failure" do
-      @resource.ignore_failure(true)
-      expect(@resource.ignore_failure).to eq(true)
+      resource.ignore_failure(true)
+      expect(resource.ignore_failure).to eq(true)
     end
 
     it "should allow you to epic_fail" do
-      @resource.epic_fail(true)
-      expect(@resource.epic_fail).to eq(true)
+      resource.epic_fail(true)
+      expect(resource.epic_fail).to eq(true)
     end
   end
 
   describe "retries" do
-    before do
-      @retriable_resource = Chef::Resource::Cat.new("precious", @run_context)
-      @retriable_resource.provider = Chef::Provider::SnakeOil
-      @retriable_resource.action = :purr
+    let(:retriable_resource) {
+      retriable_resource = Chef::Resource::Cat.new("precious", run_context)
+      retriable_resource.provider = Chef::Provider::SnakeOil
+      retriable_resource.action = :purr
+      retriable_resource
+    }
 
-      @node.automatic_attrs[:platform] = "fubuntu"
-      @node.automatic_attrs[:platform_version] = '10.04'
+    before do
+      node.automatic_attrs[:platform] = "fubuntu"
+      node.automatic_attrs[:platform_version] = '10.04'
     end
 
     it "should default to not retrying if a provider fails for a resource" do
-      expect(@retriable_resource.retries).to eq(0)
+      expect(retriable_resource.retries).to eq(0)
     end
 
     it "should allow you to set how many retries a provider should attempt after a failure" do
-      @retriable_resource.retries(2)
-      expect(@retriable_resource.retries).to eq(2)
+      retriable_resource.retries(2)
+      expect(retriable_resource.retries).to eq(2)
     end
 
     it "should default to a retry delay of 2 seconds" do
-      expect(@retriable_resource.retry_delay).to eq(2)
+      expect(retriable_resource.retry_delay).to eq(2)
     end
 
     it "should allow you to set the retry delay" do
-      @retriable_resource.retry_delay(10)
-      expect(@retriable_resource.retry_delay).to eq(10)
+      retriable_resource.retry_delay(10)
+      expect(retriable_resource.retry_delay).to eq(10)
     end
 
     it "should keep given value of retries intact after the provider fails for a resource" do
-      @retriable_resource.retries(3)
-      @retriable_resource.retry_delay(0) # No need to wait.
+      retriable_resource.retries(3)
+      retriable_resource.retry_delay(0) # No need to wait.
 
-      provider = Chef::Provider::SnakeOil.new(@retriable_resource, @run_context)
+      provider = Chef::Provider::SnakeOil.new(retriable_resource, run_context)
       allow(Chef::Provider::SnakeOil).to receive(:new).and_return(provider)
       allow(provider).to receive(:action_purr).and_raise
 
-      expect(@retriable_resource).to receive(:sleep).exactly(3).times
-      expect { @retriable_resource.run_action(:purr) }.to raise_error
-      expect(@retriable_resource.retries).to eq(3)
+      expect(retriable_resource).to receive(:sleep).exactly(3).times
+      expect { retriable_resource.run_action(:purr) }.to raise_error
+      expect(retriable_resource.retries).to eq(3)
     end
   end
 
@@ -447,8 +553,21 @@ describe Chef::Resource do
       expect(Chef::Resource.provider_base).to eq(Chef::Provider)
     end
 
-    it "allows the base provider to be overriden by a " do
-      expect(ResourceTestHarness.provider_base).to eq(Chef::Provider::Package)
+    it "allows the base provider to be overridden" do
+      Chef::Config.treat_deprecation_warnings_as_errors(false)
+      class OverrideProviderBaseTest < Chef::Resource
+        provider_base Chef::Provider::Package
+      end
+
+      expect(OverrideProviderBaseTest.provider_base).to eq(Chef::Provider::Package)
+    end
+
+    it "warns when setting provider_base" do
+      expect {
+        class OverrideProviderBaseTest2 < Chef::Resource
+          provider_base Chef::Provider::Package
+        end
+      }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
     end
 
   end
@@ -459,28 +578,28 @@ describe Chef::Resource do
 
   describe "when updated by a provider" do
     before do
-      @resource.updated_by_last_action(true)
+      resource.updated_by_last_action(true)
     end
 
     it "records that it was updated" do
-      expect(@resource).to be_updated
+      expect(resource).to be_updated
     end
 
     it "records that the last action updated the resource" do
-      expect(@resource).to be_updated_by_last_action
+      expect(resource).to be_updated_by_last_action
     end
 
     describe "and then run again without being updated" do
       before do
-        @resource.updated_by_last_action(false)
+        resource.updated_by_last_action(false)
       end
 
       it "reports that it is updated" do
-        expect(@resource).to be_updated
+        expect(resource).to be_updated
       end
 
       it "reports that it was not updated by the last action" do
-        expect(@resource).not_to be_updated_by_last_action
+        expect(resource).not_to be_updated_by_last_action
       end
 
     end
@@ -488,72 +607,75 @@ describe Chef::Resource do
   end
 
   describe "when invoking its action" do
+    let(:resource) {
+      resource = Chef::Resource.new("provided", run_context)
+      resource.provider = Chef::Provider::SnakeOil
+      resource
+    }
     before do
-      @resource = Chef::Resource.new("provided", @run_context)
-      @resource.provider = Chef::Provider::SnakeOil
-      @node.automatic_attrs[:platform] = "fubuntu"
-      @node.automatic_attrs[:platform_version] = '10.04'
+      node.automatic_attrs[:platform] = "fubuntu"
+      node.automatic_attrs[:platform_version] = '10.04'
     end
 
     it "does not run only_if if no only_if command is given" do
       expect_any_instance_of(Chef::Resource::Conditional).not_to receive(:evaluate)
-      @resource.only_if.clear
-      @resource.run_action(:purr)
+      resource.only_if.clear
+      resource.run_action(:purr)
     end
 
     it "runs runs an only_if when one is given" do
       snitch_variable = nil
-      @resource.only_if { snitch_variable = true }
-      expect(@resource.only_if.first.positivity).to eq(:only_if)
+      resource.only_if { snitch_variable = true }
+      expect(resource.only_if.first.positivity).to eq(:only_if)
       #Chef::Mixin::Command.should_receive(:only_if).with(true, {}).and_return(false)
-      @resource.run_action(:purr)
+      resource.run_action(:purr)
       expect(snitch_variable).to be_truthy
     end
 
     it "runs multiple only_if conditionals" do
       snitch_var1, snitch_var2 = nil, nil
-      @resource.only_if { snitch_var1 = 1 }
-      @resource.only_if { snitch_var2 = 2 }
-      @resource.run_action(:purr)
+      resource.only_if { snitch_var1 = 1 }
+      resource.only_if { snitch_var2 = 2 }
+      resource.run_action(:purr)
       expect(snitch_var1).to eq(1)
       expect(snitch_var2).to eq(2)
     end
 
     it "accepts command options for only_if conditionals" do
       expect_any_instance_of(Chef::Resource::Conditional).to receive(:evaluate_command).at_least(1).times
-      @resource.only_if("true", :cwd => '/tmp')
-      expect(@resource.only_if.first.command_opts).to eq({:cwd => '/tmp'})
-      @resource.run_action(:purr)
+      resource.only_if("true", :cwd => '/tmp')
+      expect(resource.only_if.first.command_opts).to eq({:cwd => '/tmp'})
+      resource.run_action(:purr)
     end
 
     it "runs not_if as a command when it is a string" do
       expect_any_instance_of(Chef::Resource::Conditional).to receive(:evaluate_command).at_least(1).times
-      @resource.not_if "pwd"
-      @resource.run_action(:purr)
+      resource.not_if "pwd"
+      resource.run_action(:purr)
     end
 
     it "runs not_if as a block when it is a ruby block" do
       expect_any_instance_of(Chef::Resource::Conditional).to receive(:evaluate_block).at_least(1).times
-      @resource.not_if { puts 'foo' }
-      @resource.run_action(:purr)
+      resource.not_if { puts 'foo' }
+      resource.run_action(:purr)
     end
 
     it "does not run not_if if no not_if command is given" do
       expect_any_instance_of(Chef::Resource::Conditional).not_to receive(:evaluate)
-      @resource.not_if.clear
-      @resource.run_action(:purr)
+      resource.not_if.clear
+      resource.run_action(:purr)
     end
 
     it "accepts command options for not_if conditionals" do
-      @resource.not_if("pwd" , :cwd => '/tmp')
-      expect(@resource.not_if.first.command_opts).to eq({:cwd => '/tmp'})
+      resource.not_if("pwd" , :cwd => '/tmp')
+      expect(resource.not_if.first.command_opts).to eq({:cwd => '/tmp'})
     end
 
     it "accepts multiple not_if conditionals" do
       snitch_var1, snitch_var2 = true, true
-      @resource.not_if {snitch_var1 = nil}
-      @resource.not_if {snitch_var2 = false}
-      @resource.run_action(:purr)
+      resource.not_if {snitch_var1 = nil}
+      resource.not_if {snitch_var2 = false}
+      resource.run_action(:purr)
       expect(snitch_var1).to be_nil
       expect(snitch_var2).to be_falsey
     end
@@ -561,13 +683,11 @@ describe Chef::Resource do
     it "reports 0 elapsed time if actual elapsed time is < 0" do
       expected = Time.now
       allow(Time).to receive(:now).and_return(expected, expected - 1)
-      @resource.run_action(:purr)
-      expect(@resource.elapsed_time).to eq(0)
+      resource.run_action(:purr)
+      expect(resource.elapsed_time).to eq(0)
     end
 
     describe "guard_interpreter attribute" do
-      let(:resource) { @resource }
-
       it "should be set to :default by default" do
         expect(resource.guard_interpreter).to eq(:default)
       end
@@ -590,106 +710,108 @@ describe Chef::Resource do
 
   describe "should_skip?" do
     before do
-      @resource = Chef::Resource::Cat.new("sugar", @run_context)
+      resource = Chef::Resource::Cat.new("sugar", run_context)
     end
 
     it "should return false by default" do
-      expect(@resource.should_skip?(:purr)).to be_falsey
+      expect(resource.should_skip?(:purr)).to be_falsey
     end
 
     it "should return false when only_if is met" do
-      @resource.only_if { true }
-      expect(@resource.should_skip?(:purr)).to be_falsey
+      resource.only_if { true }
+      expect(resource.should_skip?(:purr)).to be_falsey
     end
 
     it "should return true when only_if is not met" do
-      @resource.only_if { false }
-      expect(@resource.should_skip?(:purr)).to be_truthy
+      resource.only_if { false }
+      expect(resource.should_skip?(:purr)).to be_truthy
     end
 
     it "should return true when not_if is met" do
-      @resource.not_if { true }
-      expect(@resource.should_skip?(:purr)).to be_truthy
+      resource.not_if { true }
+      expect(resource.should_skip?(:purr)).to be_truthy
     end
 
     it "should return false when not_if is not met" do
-      @resource.not_if { false }
-      expect(@resource.should_skip?(:purr)).to be_falsey
+      resource.not_if { false }
+      expect(resource.should_skip?(:purr)).to be_falsey
     end
 
     it "should return true when only_if is met but also not_if is met" do
-      @resource.only_if { true }
-      @resource.not_if { true }
-      expect(@resource.should_skip?(:purr)).to be_truthy
+      resource.only_if { true }
+      resource.not_if { true }
+      expect(resource.should_skip?(:purr)).to be_truthy
     end
 
     it "should return true when one of multiple only_if's is not met" do
-      @resource.only_if { true }
-      @resource.only_if { false }
-      @resource.only_if { true }
-      expect(@resource.should_skip?(:purr)).to be_truthy
+      resource.only_if { true }
+      resource.only_if { false }
+      resource.only_if { true }
+      expect(resource.should_skip?(:purr)).to be_truthy
     end
 
     it "should return true when one of multiple not_if's is met" do
-      @resource.not_if { false }
-      @resource.not_if { true }
-      @resource.not_if { false }
-      expect(@resource.should_skip?(:purr)).to be_truthy
+      resource.not_if { false }
+      resource.not_if { true }
+      resource.not_if { false }
+      expect(resource.should_skip?(:purr)).to be_truthy
     end
 
     it "should return true when action is :nothing" do
-      expect(@resource.should_skip?(:nothing)).to be_truthy
+      expect(resource.should_skip?(:nothing)).to be_truthy
     end
 
     it "should return true when action is :nothing ignoring only_if/not_if conditionals" do
-      @resource.only_if { true }
-      @resource.not_if { false }
-      expect(@resource.should_skip?(:nothing)).to be_truthy
+      resource.only_if { true }
+      resource.not_if { false }
+      expect(resource.should_skip?(:nothing)).to be_truthy
     end
 
     it "should print \"skipped due to action :nothing\" message for doc formatter when action is :nothing" do
       fdoc = Chef::Formatters.new(:doc, STDOUT, STDERR)
-      allow(@run_context).to receive(:events).and_return(fdoc)
+      allow(run_context).to receive(:events).and_return(fdoc)
       expect(fdoc).to receive(:puts).with(" (skipped due to action :nothing)", anything())
-      @resource.should_skip?(:nothing)
+      resource.should_skip?(:nothing)
     end
 
   end
 
   describe "when resource action is :nothing" do
+    let(:resource1) {
+      resource1 = Chef::Resource::Cat.new("sugar", run_context)
+      resource1.action = :nothing
+      resource1
+    }
     before do
-      @resource1 = Chef::Resource::Cat.new("sugar", @run_context)
-      @resource1.action = :nothing
-
-      @node.automatic_attrs[:platform] = "fubuntu"
-      @node.automatic_attrs[:platform_version] = '10.04'
+      node.automatic_attrs[:platform] = "fubuntu"
+      node.automatic_attrs[:platform_version] = '10.04'
     end
 
     it "should not run only_if/not_if conditionals (CHEF-972)" do
       snitch_var1 = 0
-      @resource1.only_if { snitch_var1 = 1 }
-      @resource1.not_if { snitch_var1 = 2 }
-      @resource1.run_action(:nothing)
+      resource1.only_if { snitch_var1 = 1 }
+      resource1.not_if { snitch_var1 = 2 }
+      resource1.run_action(:nothing)
       expect(snitch_var1).to eq(0)
     end
 
     it "should run only_if/not_if conditionals when notified to run another action (CHEF-972)" do
       snitch_var1 = snitch_var2 = 0
-      @runner = Chef::Runner.new(@run_context)
+      runner = Chef::Runner.new(run_context)
       Chef::Platform.set(
         :resource => :cat,
         :provider => Chef::Provider::SnakeOil
       )
 
-      @resource1.only_if { snitch_var1 = 1 }
-      @resource1.not_if { snitch_var2 = 2 }
-      @resource2 = Chef::Resource::Cat.new("coffee", @run_context)
-      @resource2.notifies :purr, @resource1
-      @resource2.action = :purr
+      resource1.only_if { snitch_var1 = 1 }
+      resource1.not_if { snitch_var2 = 2 }
+      resource2 = Chef::Resource::Cat.new("coffee", run_context)
+      resource2.notifies :purr, resource1
+      resource2.action = :purr
 
-      @run_context.resource_collection << @resource1
-      @run_context.resource_collection << @resource2
-      @runner.converge
+      run_context.resource_collection << resource1
+      run_context.resource_collection << resource2
+      runner.converge
 
       expect(snitch_var1).to eq(1)
       expect(snitch_var2).to eq(2)
@@ -709,57 +831,72 @@ describe Chef::Resource do
     end
 
     it 'adds mappings for a single platform' do
-      expect(Chef::Resource::Klz.node_map).to receive(:set).with(
-        :dinobot, true, { platform: ['autobots'] }
+      expect(Chef.resource_handler_map).to receive(:set).with(
+        :dinobot, Chef::Resource::Klz, { platform: ['autobots'] }
       )
       klz.provides :dinobot, platform: ['autobots']
     end
 
     it 'adds mappings for multiple platforms' do
-      expect(Chef::Resource::Klz.node_map).to receive(:set).with(
-        :energy, true, { platform: ['autobots', 'decepticons']}
+      expect(Chef.resource_handler_map).to receive(:set).with(
+        :energy, Chef::Resource::Klz, { platform: ['autobots', 'decepticons']}
       )
       klz.provides :energy, platform: ['autobots', 'decepticons']
     end
 
     it 'adds mappings for all platforms' do
-      expect(Chef::Resource::Klz.node_map).to receive(:set).with(
-        :tape_deck, true, {}
+      expect(Chef.resource_handler_map).to receive(:set).with(
+        :tape_deck, Chef::Resource::Klz, {}
       )
       klz.provides :tape_deck
     end
 
   end
 
-  describe "lookups from the platform map" do
-    let(:klz1) { Class.new(Chef::Resource) }
-    let(:klz2) { Class.new(Chef::Resource) }
+  describe "resource_for_node" do
+    describe "lookups from the platform map" do
+      let(:klz1) { Class.new(Chef::Resource) }
+
+      before(:each) do
+        Chef::Resource::Klz1 = klz1
+        node = Chef::Node.new
+        node.name("bumblebee")
+        node.automatic[:platform] = "autobots"
+        node.automatic[:platform_version] = "6.1"
+        Object.const_set('Soundwave', klz1)
+        klz1.provides :soundwave
+      end
 
-    before(:each) do
-      Chef::Resource::Klz1 = klz1
-      Chef::Resource::Klz2 = klz2
-      @node = Chef::Node.new
-      @node.name("bumblebee")
-      @node.automatic[:platform] = "autobots"
-      @node.automatic[:platform_version] = "6.1"
-      Object.const_set('Soundwave', klz1)
-      klz2.provides :dinobot, :on_platforms => ['autobots']
-      Object.const_set('Grimlock', klz2)
-    end
+      after(:each) do
+        Object.send(:remove_const, :Soundwave)
+        Chef::Resource.send(:remove_const, :Klz1)
+      end
 
-    after(:each) do
-      Object.send(:remove_const, :Soundwave)
-      Object.send(:remove_const, :Grimlock)
-      Chef::Resource.send(:remove_const, :Klz1)
-      Chef::Resource.send(:remove_const, :Klz2)
+      it "returns a resource by short_name if nothing else matches" do
+        expect(Chef::Resource.resource_for_node(:soundwave, node)).to eql(klz1)
+      end
     end
 
-    describe "resource_for_node" do
-      it "returns a resource by short_name and node" do
-        expect(Chef::Resource.resource_for_node(:dinobot, @node)).to eql(Grimlock)
+    describe "lookups from the platform map" do
+      let(:klz2) { Class.new(Chef::Resource) }
+
+      before(:each) do
+        Chef::Resource::Klz2 = klz2
+        node.name("bumblebee")
+        node.automatic[:platform] = "autobots"
+        node.automatic[:platform_version] = "6.1"
+        klz2.provides :dinobot, :platform => ['autobots']
+        Object.const_set('Grimlock', klz2)
+        klz2.provides :grimlock
       end
-      it "returns a resource by short_name if nothing else matches" do
-        expect(Chef::Resource.resource_for_node(:soundwave, @node)).to eql(Soundwave)
+
+      after(:each) do
+        Object.send(:remove_const, :Grimlock)
+        Chef::Resource.send(:remove_const, :Klz2)
+      end
+
+      it "returns a resource by short_name and node" do
+        expect(Chef::Resource.resource_for_node(:dinobot, node)).to eql(klz2)
       end
     end
 
@@ -770,69 +907,64 @@ describe Chef::Resource do
     describe "with a string resource spec" do
 
       it "creates a delayed notification when timing is not specified" do
-        @resource.notifies(:run, "execute[foo]")
-        expect(@run_context.delayed_notification_collection.size).to eq(1)
+        resource.notifies(:run, "execute[foo]")
+        expect(run_context.delayed_notification_collection.size).to eq(1)
       end
 
       it "creates a delayed notification when :delayed is not specified" do
-        @resource.notifies(:run, "execute[foo]", :delayed)
-        expect(@run_context.delayed_notification_collection.size).to eq(1)
+        resource.notifies(:run, "execute[foo]", :delayed)
+        expect(run_context.delayed_notification_collection.size).to eq(1)
       end
 
       it "creates an immediate notification when :immediate is specified" do
-        @resource.notifies(:run, "execute[foo]", :immediate)
-        expect(@run_context.immediate_notification_collection.size).to eq(1)
+        resource.notifies(:run, "execute[foo]", :immediate)
+        expect(run_context.immediate_notification_collection.size).to eq(1)
       end
 
       it "creates an immediate notification when :immediately is specified" do
-        @resource.notifies(:run, "execute[foo]", :immediately)
-        expect(@run_context.immediate_notification_collection.size).to eq(1)
+        resource.notifies(:run, "execute[foo]", :immediately)
+        expect(run_context.immediate_notification_collection.size).to eq(1)
       end
 
       describe "with a syntax error in the resource spec" do
 
         it "raises an exception immmediately" do
           expect do
-            @resource.notifies(:run, "typo[missing-closing-bracket")
+            resource.notifies(:run, "typo[missing-closing-bracket")
           end.to raise_error(Chef::Exceptions::InvalidResourceSpecification)
         end
       end
     end
 
     describe "with a resource reference" do
-      before do
-        @notified_resource = Chef::Resource.new("punk", @run_context)
-      end
+      let(:notified_resource) { Chef::Resource.new("punk", run_context) }
 
       it "creates a delayed notification when timing is not specified" do
-        @resource.notifies(:run, @notified_resource)
-        expect(@run_context.delayed_notification_collection.size).to eq(1)
+        resource.notifies(:run, notified_resource)
+        expect(run_context.delayed_notification_collection.size).to eq(1)
       end
 
       it "creates a delayed notification when :delayed is not specified" do
-        @resource.notifies(:run, @notified_resource, :delayed)
-        expect(@run_context.delayed_notification_collection.size).to eq(1)
+        resource.notifies(:run, notified_resource, :delayed)
+        expect(run_context.delayed_notification_collection.size).to eq(1)
       end
 
       it "creates an immediate notification when :immediate is specified" do
-        @resource.notifies(:run, @notified_resource, :immediate)
-        expect(@run_context.immediate_notification_collection.size).to eq(1)
+        resource.notifies(:run, notified_resource, :immediate)
+        expect(run_context.immediate_notification_collection.size).to eq(1)
       end
 
       it "creates an immediate notification when :immediately is specified" do
-        @resource.notifies(:run, @notified_resource, :immediately)
-        expect(@run_context.immediate_notification_collection.size).to eq(1)
+        resource.notifies(:run, notified_resource, :immediately)
+        expect(run_context.immediate_notification_collection.size).to eq(1)
       end
     end
 
   end
 
   describe "resource sensitive attribute" do
-
-    before(:each) do
-       @resource_file = Chef::Resource::File.new("/nonexistent/CHEF-5098/file", @run_context)
-       @action = :create
-    end
+    let(:resource_file) { Chef::Resource::File.new("/nonexistent/CHEF-5098/file", run_context) }
+    let(:action) { :create }
 
     def compiled_resource_data(resource, action, err)
       error_inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(resource, action, err)
@@ -843,21 +975,107 @@ describe Chef::Resource do
     end
 
     it "set to false by default" do
-      expect(@resource.sensitive).to be_falsey
+      expect(resource.sensitive).to be_falsey
     end
 
     it "when set to false should show compiled resource for failed resource" do
-      expect { @resource_file.run_action(@action) }.to raise_error { |err|
-            expect(compiled_resource_data(@resource_file, @action, err)).to match 'path "/nonexistent/CHEF-5098/file"'
+      expect { resource_file.run_action(action) }.to raise_error { |err|
+            expect(compiled_resource_data(resource_file, action, err)).to match 'path "/nonexistent/CHEF-5098/file"'
           }
     end
 
     it "when set to true should show compiled resource for failed resource" do
-      @resource_file.sensitive true
-      expect { @resource_file.run_action(@action) }.to raise_error { |err|
-            expect(compiled_resource_data(@resource_file, @action, err)).to eql("suppressed sensitive resource output")
+      resource_file.sensitive true
+      expect { resource_file.run_action(action) }.to raise_error { |err|
+            expect(compiled_resource_data(resource_file, action, err)).to eql("suppressed sensitive resource output")
           }
     end
 
   end
+
+  describe "#action" do
+    let(:resource_class) do
+      Class.new(described_class) do
+        allowed_actions(%i{one two})
+      end
+    end
+    let(:resource) { resource_class.new('test', nil) }
+    subject { resource.action }
+
+    context "with a no action" do
+      it { is_expected.to eq [:nothing] }
+    end
+
+    context "with a default action" do
+      let(:resource_class) do
+        Class.new(described_class) do
+          default_action(:one)
+        end
+      end
+      it { is_expected.to eq [:one] }
+    end
+
+    context "with a symbol action" do
+      before { resource.action(:one) }
+      it { is_expected.to eq [:one] }
+    end
+
+    context "with a string action" do
+      before { resource.action('two') }
+      it { is_expected.to eq [:two] }
+    end
+
+    context "with an array action" do
+      before { resource.action([:two, :one]) }
+      it { is_expected.to eq [:two, :one] }
+    end
+
+    context "with an assignment" do
+      before { resource.action = :one }
+      it { is_expected.to eq [:one] }
+    end
+
+    context "with an array assignment" do
+      before { resource.action = [:two, :one] }
+      it { is_expected.to eq [:two, :one] }
+    end
+
+    context "with an invalid action" do
+      it { expect { resource.action(:three) }.to raise_error Chef::Exceptions::ValidationFailed }
+    end
+
+    context "with an invalid assignment action" do
+      it { expect { resource.action = :three }.to raise_error Chef::Exceptions::ValidationFailed }
+    end
+  end
+
+  describe ".default_action" do
+    let(:default_action) { }
+    let(:resource_class) do
+      actions = default_action
+      Class.new(described_class) do
+        default_action(actions) if actions
+      end
+    end
+    subject { resource_class.default_action }
+
+    context "with no default actions" do
+      it { is_expected.to eq [:nothing] }
+    end
+
+    context "with a symbol default action" do
+      let(:default_action) { :one }
+      it { is_expected.to eq [:one] }
+    end
+
+    context "with a string default action" do
+      let(:default_action) { 'one' }
+      it { is_expected.to eq [:one] }
+    end
+
+    context "with an array default action" do
+      let(:default_action) { [:two, :one] }
+      it { is_expected.to eq [:two, :one] }
+    end
+  end
 end
diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb
index 85c9e3d..3eee997 100644
--- a/spec/unit/rest_spec.rb
+++ b/spec/unit/rest_spec.rb
@@ -69,8 +69,8 @@ describe Chef::REST do
     rest
   end
 
-  let(:standard_read_headers) {{"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id}}
-  let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id}}
+  let(:standard_read_headers) {{"Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}}
+  let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}}
 
   before(:each) do
     Chef::Log.init(log_stringio)
@@ -277,19 +277,6 @@ describe Chef::REST do
       rest
     end
 
-    let(:base_headers) do
-      {
-        'Accept' => 'application/json',
-        'X-Chef-Version' => Chef::VERSION,
-        'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
-        'X-REMOTE-REQUEST-ID' => request_id
-      }
-    end
-
-    let (:req_with_body_headers) do
-      base_headers.merge("Content-Type" => "application/json", "Content-Length" => '13')
-    end
-
     before(:each) do
       Chef::Config[:ssl_client_cert] = nil
       Chef::Config[:ssl_client_key]  = nil
@@ -304,7 +291,8 @@ describe Chef::REST do
           'X-Chef-Version' => Chef::VERSION,
           'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
           'Host' => host_header,
-          'X-REMOTE-REQUEST-ID' => request_id
+          'X-REMOTE-REQUEST-ID' => request_id,
+          'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION
         }
       end
 
@@ -548,7 +536,7 @@ describe Chef::REST do
           end
         end
       end
-    end
+    end # as JSON API requests
 
     context "when streaming downloads to a tempfile" do
       let!(:tempfile) {  Tempfile.open("chef-rspec-rest_spec-line-@{__LINE__}--") }
@@ -586,7 +574,8 @@ describe Chef::REST do
                             'X-Chef-Version' => Chef::VERSION,
                             'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
                             'Host' => host_header,
-                            'X-REMOTE-REQUEST-ID'=> request_id
+                            'X-REMOTE-REQUEST-ID'=> request_id,
+                            'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION
                             }
         expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
         rest.streaming_request(url, {})
@@ -597,7 +586,8 @@ describe Chef::REST do
                             'X-Chef-Version' => Chef::VERSION,
                             'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
                             'Host' => host_header,
-                            'X-REMOTE-REQUEST-ID'=> request_id
+                            'X-REMOTE-REQUEST-ID'=> request_id,
+                            'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION
                             }
         expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
         rest.streaming_request(url, {})
@@ -695,7 +685,7 @@ describe Chef::REST do
         expect(block_called).to be_truthy
       end
     end
-  end
+  end # when making REST requests
 
   context "when following redirects" do
     let(:rest) do
diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb
index 5421b5a..ecc7945 100644
--- a/spec/unit/role_spec.rb
+++ b/spec/unit/role_spec.rb
@@ -21,7 +21,7 @@ require 'chef/role'
 
 describe Chef::Role do
   before(:each) do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
     @role = Chef::Role.new
     @role.name("ops_master")
   end
@@ -217,7 +217,7 @@ describe Chef::Role do
 
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) { @role }
     end
   end
diff --git a/spec/unit/run_context/child_run_context_spec.rb b/spec/unit/run_context/child_run_context_spec.rb
new file mode 100644
index 0000000..63586e4
--- /dev/null
+++ b/spec/unit/run_context/child_run_context_spec.rb
@@ -0,0 +1,133 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, 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 'spec_helper'
+require 'support/lib/library_load_order'
+
+describe Chef::RunContext::ChildRunContext do
+  context "with a run context with stuff in it" do
+    let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks")) }
+    let(:cookbook_collection) {
+      cl = Chef::CookbookLoader.new(chef_repo_path)
+      cl.load_cookbooks
+      Chef::CookbookCollection.new(cl)
+    }
+    let(:node) {
+      node = Chef::Node.new
+      node.run_list << "test" << "test::one" << "test::two"
+      node
+    }
+    let(:events) { Chef::EventDispatch::Dispatcher.new }
+    let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) }
+
+    context "and a child run context" do
+      let(:child) { run_context.create_child }
+
+      it "parent_run_context is set to the parent" do
+        expect(child.parent_run_context).to eq run_context
+      end
+
+      it "audits is not the same as the parent" do
+        expect(child.audits.object_id).not_to eq run_context.audits.object_id
+        child.audits['hi'] = 'lo'
+        expect(child.audits['hi']).to eq('lo')
+        expect(run_context.audits['hi']).not_to eq('lo')
+      end
+
+      it "resource_collection is not the same as the parent" do
+        expect(child.resource_collection.object_id).not_to eq run_context.resource_collection.object_id
+        f = Chef::Resource::File.new('hi', child)
+        child.resource_collection.insert(f)
+        expect(child.resource_collection).to include f
+        expect(run_context.resource_collection).not_to include f
+      end
+
+      it "immediate_notification_collection is not the same as the parent" do
+        expect(child.immediate_notification_collection.object_id).not_to eq run_context.immediate_notification_collection.object_id
+        src = Chef::Resource::File.new('hi', child)
+        dest = Chef::Resource::File.new('argh', child)
+        notification = Chef::Resource::Notification.new(dest, :create, src)
+        child.notifies_immediately(notification)
+        expect(child.immediate_notification_collection['file[hi]']).to eq([notification])
+        expect(run_context.immediate_notification_collection['file[hi]']).not_to eq([notification])
+      end
+
+      it "immediate_notifications is not the same as the parent" do
+        src = Chef::Resource::File.new('hi', child)
+        dest = Chef::Resource::File.new('argh', child)
+        notification = Chef::Resource::Notification.new(dest, :create, src)
+        child.notifies_immediately(notification)
+        expect(child.immediate_notifications(src)).to eq([notification])
+        expect(run_context.immediate_notifications(src)).not_to eq([notification])
+      end
+
+      it "delayed_notification_collection is not the same as the parent" do
+        expect(child.delayed_notification_collection.object_id).not_to eq run_context.delayed_notification_collection.object_id
+        src = Chef::Resource::File.new('hi', child)
+        dest = Chef::Resource::File.new('argh', child)
+        notification = Chef::Resource::Notification.new(dest, :create, src)
+        child.notifies_delayed(notification)
+        expect(child.delayed_notification_collection['file[hi]']).to eq([notification])
+        expect(run_context.delayed_notification_collection['file[hi]']).not_to eq([notification])
+      end
+
+      it "delayed_notifications is not the same as the parent" do
+        src = Chef::Resource::File.new('hi', child)
+        dest = Chef::Resource::File.new('argh', child)
+        notification = Chef::Resource::Notification.new(dest, :create, src)
+        child.notifies_delayed(notification)
+        expect(child.delayed_notifications(src)).to eq([notification])
+        expect(run_context.delayed_notifications(src)).not_to eq([notification])
+      end
+
+      it "create_child creates a child-of-child" do
+        c = child.create_child
+        expect(c.parent_run_context).to eq child
+      end
+
+      context "after load('include::default')" do
+        before do
+          run_list = Chef::RunList.new('include::default').expand('_default')
+          # TODO not sure why we had to do this to get everything to work ...
+          node.automatic_attrs[:recipes] = []
+          child.load(run_list)
+        end
+
+        it "load_recipe loads into the child" do
+          expect(child.resource_collection).to be_empty
+          child.load_recipe("include::includee")
+          expect(child.resource_collection).not_to be_empty
+        end
+
+        it "include_recipe loads into the child" do
+          expect(child.resource_collection).to be_empty
+          child.include_recipe("include::includee")
+          expect(child.resource_collection).not_to be_empty
+        end
+
+        it "load_recipe_file loads into the child" do
+          expect(child.resource_collection).to be_empty
+          child.load_recipe_file(File.expand_path("include/recipes/includee.rb", chef_repo_path))
+          expect(child.resource_collection).not_to be_empty
+        end
+      end
+    end
+  end
+end
diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb
index d656111..9980157 100644
--- a/spec/unit/run_context_spec.rb
+++ b/spec/unit/run_context_spec.rb
@@ -53,6 +53,44 @@ describe Chef::RunContext do
     expect(run_context.node).to eq(node)
   end
 
+  it "loads up node[:cookbooks]" do
+    expect(run_context.node[:cookbooks]).to eql(
+      {
+        "circular-dep1" => {
+          "version" => "0.0.0",
+        },
+        "circular-dep2" => {
+          "version" => "0.0.0",
+        },
+        "dependency1" => {
+          "version" => "0.0.0",
+        },
+        "dependency2" => {
+          "version" => "0.0.0",
+        },
+        "include" => {
+          "version" => "0.0.0",
+        },
+        "no-default-attr" => {
+          "version" => "0.0.0",
+        },
+        "test" => {
+          "version" => "0.0.0",
+        },
+        "test-with-circular-deps" => {
+          "version" => "0.0.0",
+        },
+        "test-with-deps" => {
+          "version" => "0.0.0",
+        },
+      }
+    )
+  end
+
+  it "has a nil parent_run_context" do
+    expect(run_context.parent_run_context).to be_nil
+  end
+
   describe "loading cookbooks for a run list" do
     before do
 
@@ -159,4 +197,45 @@ describe Chef::RunContext do
       expect(run_context.reboot_requested?).to be_falsey
     end
   end
+
+  describe "notifications" do
+    let(:notification) { Chef::Resource::Notification.new(nil, nil, notifying_resource) }
+
+    shared_context "notifying resource is a Chef::Resource" do
+      let(:notifying_resource)  { Chef::Resource.new("gerbil") }
+
+      it "should be keyed off the resource name" do
+        run_context.send(setter, notification)
+        expect(run_context.send(getter, notifying_resource)).to eq([notification])
+      end
+    end
+
+    shared_context "notifying resource is a subclass of Chef::Resource" do
+      let(:declared_type) { :alpaca }
+      let(:notifying_resource)  {
+        r = Class.new(Chef::Resource).new("guinea pig")
+        r.declared_type = declared_type
+        r
+      }
+
+      it "should be keyed off the resource declared key" do
+        run_context.send(setter, notification)
+        expect(run_context.send(getter, notifying_resource)).to eq([notification])
+      end
+    end
+
+    describe "of the immediate kind" do
+      let(:setter) { :notifies_immediately }
+      let(:getter) { :immediate_notifications }
+      include_context "notifying resource is a Chef::Resource"
+      include_context "notifying resource is a subclass of Chef::Resource"
+    end
+
+    describe "of the delayed kind" do
+      let(:setter) { :notifies_delayed }
+      let(:getter) { :delayed_notifications }
+      include_context "notifying resource is a Chef::Resource"
+      include_context "notifying resource is a subclass of Chef::Resource"
+    end
+  end
 end
diff --git a/spec/unit/run_list/run_list_expansion_spec.rb b/spec/unit/run_list/run_list_expansion_spec.rb
index 859219d..a7df9e7 100644
--- a/spec/unit/run_list/run_list_expansion_spec.rb
+++ b/spec/unit/run_list/run_list_expansion_spec.rb
@@ -21,7 +21,7 @@ require 'spec_helper'
 describe Chef::RunList::RunListExpansion do
   before do
     @run_list = Chef::RunList.new
-    @run_list << 'recipe[lobster]' << 'role[rage]' << 'recipe[fist]'
+    @run_list << 'recipe[lobster::mastercookbook at 0.1.0]' << 'role[rage]' << 'recipe[fist at 0.1]'
     @expansion = Chef::RunList::RunListExpansion.new("_default", @run_list.run_list_items)
   end
 
@@ -59,7 +59,7 @@ describe Chef::RunList::RunListExpansion do
     end
 
     it "has the correct list of recipes for the given environment" do
-      expect(@expansion.recipes).to eq(["lobster", "prod-only", "fist"])
+      expect(@expansion.recipes).to eq(["lobster::mastercookbook", "prod-only", "fist"])
     end
 
   end
@@ -82,19 +82,34 @@ describe Chef::RunList::RunListExpansion do
   describe "after expanding a run list" do
     before do
       @first_role = Chef::Role.new
+      @first_role.name('rage')
       @first_role.run_list('role[mollusk]')
       @first_role.default_attributes({'foo' => 'bar'})
       @first_role.override_attributes({'baz' => 'qux'})
       @second_role = Chef::Role.new
+      @second_role.name('rage')
       @second_role.run_list('recipe[crabrevenge]')
       @second_role.default_attributes({'foo' => 'boo'})
       @second_role.override_attributes({'baz' => 'bux'})
       allow(@expansion).to receive(:fetch_role).and_return(@first_role, @second_role)
       @expansion.expand
+      @json = '{"id":"_default","run_list":[{"type":"recipe","name":"lobster::mastercookbook","version":"0.1.0",'
+      .concat(
+'"skipped":false},{"type":"role","name":"rage","children":[{"type":"role","name":"mollusk","children":[],"missing":null,'
+      .concat(
+'"error":null,"skipped":null},{"type":"recipe","name":"crabrevenge","version":null,"skipped":false}],"missing":null,'
+      .concat(
+'"error":null,"skipped":null},{"type":"recipe","name":"fist","version":"0.1","skipped":false}]}')))
+
+    end
+
+    it "produces json tree upon tracing expansion" do
+      jsonRunList = @expansion.to_json
+      expect(jsonRunList).to eq(@json)
     end
 
     it "has the ordered list of recipes" do
-      expect(@expansion.recipes).to eq(['lobster', 'crabrevenge', 'fist'])
+      expect(@expansion.recipes).to eq(['lobster::mastercookbook', 'crabrevenge', 'fist'])
     end
 
     it "has the merged attributes from the roles with outer roles overriding inner" do
diff --git a/spec/unit/run_list/versioned_recipe_list_spec.rb b/spec/unit/run_list/versioned_recipe_list_spec.rb
index 209ac37..be57d6c 100644
--- a/spec/unit/run_list/versioned_recipe_list_spec.rb
+++ b/spec/unit/run_list/versioned_recipe_list_spec.rb
@@ -26,98 +26,170 @@ describe Chef::RunList::VersionedRecipeList do
     end
   end
 
+  let(:list) { described_class.new }
+
+  let(:versioned_recipes) { [] }
+
+  let(:recipes) { [] }
+
+  before do
+    recipes.each { |r| list << r }
+    versioned_recipes.each {|r| list.add_recipe r[:name], r[:version]}
+  end
+
   describe "add_recipe" do
-    before(:each) do
-      @list = Chef::RunList::VersionedRecipeList.new
-      @list << "apt"
-      @list << "god"
-      @list << "apache2"
-    end
+
+    let(:recipes) { %w[ apt god apache2 ] }
 
     it "should append the recipe to the end of the list" do
-      @list.add_recipe "rails"
-      expect(@list).to eq(["apt", "god", "apache2", "rails"])
+      list.add_recipe "rails"
+      expect(list).to eq(["apt", "god", "apache2", "rails"])
     end
 
     it "should not duplicate entries" do
-      @list.add_recipe "apt"
-      expect(@list).to eq(["apt", "god", "apache2"])
+      list.add_recipe "apt"
+      expect(list).to eq(["apt", "god", "apache2"])
     end
 
     it "should allow you to specify a version" do
-      @list.add_recipe "rails", "1.0.0"
-      expect(@list).to eq(["apt", "god", "apache2", "rails"])
-      expect(@list.with_versions).to include({:name => "rails", :version => "1.0.0"})
+      list.add_recipe "rails", "1.0.0"
+      expect(list).to eq(["apt", "god", "apache2", "rails"])
+      expect(list.with_versions).to include({:name => "rails", :version => "1.0.0"})
     end
 
     it "should allow you to specify a version for a recipe that already exists" do
-      @list.add_recipe "apt", "1.2.3"
-      expect(@list).to eq(["apt", "god", "apache2"])
-      expect(@list.with_versions).to include({:name => "apt", :version => "1.2.3"})
+      list.add_recipe "apt", "1.2.3"
+      expect(list).to eq(["apt", "god", "apache2"])
+      expect(list.with_versions).to include({:name => "apt", :version => "1.2.3"})
     end
 
     it "should allow you to specify the same version of a recipe twice" do
-      @list.add_recipe "rails", "1.0.0"
-      @list.add_recipe "rails", "1.0.0"
-      expect(@list.with_versions).to include({:name => "rails", :version => "1.0.0"})
+      list.add_recipe "rails", "1.0.0"
+      list.add_recipe "rails", "1.0.0"
+      expect(list.with_versions).to include({:name => "rails", :version => "1.0.0"})
     end
 
     it "should allow you to spcify no version, even when a version already exists" do
-      @list.add_recipe "rails", "1.0.0"
-      @list.add_recipe "rails"
-      expect(@list.with_versions).to include({:name => "rails", :version => "1.0.0"})
+      list.add_recipe "rails", "1.0.0"
+      list.add_recipe "rails"
+      expect(list.with_versions).to include({:name => "rails", :version => "1.0.0"})
     end
 
     it "should not allow multiple versions of the same recipe" do
-      @list.add_recipe "rails", "1.0.0"
-      expect {@list.add_recipe "rails", "0.1.0"}.to raise_error Chef::Exceptions::CookbookVersionConflict
+      list.add_recipe "rails", "1.0.0"
+      expect {list.add_recipe "rails", "0.1.0"}.to raise_error Chef::Exceptions::CookbookVersionConflict
     end
   end
 
   describe "with_versions" do
-    before(:each) do
-      @recipes = [
+
+    let(:versioned_recipes) do
+      [
         {:name => "apt", :version => "1.0.0"},
         {:name => "god", :version => nil},
         {:name => "apache2", :version => "0.0.1"}
       ]
-      @list = Chef::RunList::VersionedRecipeList.new
-      @recipes.each {|i| @list.add_recipe i[:name], i[:version]}
     end
-
     it "should return an array of hashes with :name and :version" do
-      expect(@list.with_versions).to eq(@recipes)
+      expect(list.with_versions).to eq(versioned_recipes)
     end
 
     it "should retain the same order as the version-less list" do
-      with_versions = @list.with_versions
-      @list.each_with_index do |item, index|
+      with_versions = list.with_versions
+      list.each_with_index do |item, index|
         expect(with_versions[index][:name]).to eq(item)
       end
     end
   end
 
   describe "with_version_constraints" do
-    before(:each) do
-      @recipes = [
-                  {:name => "apt", :version => "~> 1.2.0"},
-                  {:name => "god", :version => nil},
-                  {:name => "apache2", :version => "0.0.1"}
-                 ]
-      @list = Chef::RunList::VersionedRecipeList.new
-      @recipes.each {|i| @list.add_recipe i[:name], i[:version]}
-      @constraints = @recipes.map do |x|
-        { :name => x[:name],
-          :version_constraint => Chef::VersionConstraint.new(x[:version])
-        }
-      end
+
+    let(:versioned_recipes) do
+      [
+        {:name => "apt", :version => "~> 1.2.0"},
+        {:name => "god", :version => nil},
+        {:name => "apache2", :version => "0.0.1"}
+      ]
     end
 
+
     it "should return an array of hashes with :name and :version_constraint" do
-      @list.with_version_constraints.each do |x|
-        expect(x).to have_key :name
-        expect(x[:version_constraint]).not_to be nil
+      list.with_version_constraints.each_with_index do |recipe_spec, i|
+
+        expected_recipe = versioned_recipes[i]
+
+        expect(recipe_spec[:name]).to eq(expected_recipe[:name])
+        expect(recipe_spec[:version_constraint]).to eq(Chef::VersionConstraint.new(expected_recipe[:version]))
       end
     end
   end
+
+  describe "with_fully_qualified_names_and_version_constraints" do
+
+    let(:fq_names) { list.with_fully_qualified_names_and_version_constraints }
+
+    context "with bare cookbook names" do
+
+      let(:recipes) { %w[ apache2 ] }
+
+      it "gives $cookbook_name::default" do
+        expect(fq_names).to eq( %w[ apache2::default ] )
+      end
+
+    end
+
+    context "with qualified recipe names but no versions" do
+
+      let(:recipes) { %w[ mysql::server ] }
+
+      it "returns the qualified recipe names" do
+        expect(fq_names).to eq( %w[ mysql::server ] )
+      end
+
+    end
+
+    context "with unqualified names that have version constraints" do
+
+      let(:versioned_recipes) do
+        [
+          {:name => "apt", :version => "~> 1.2.0"},
+        ]
+      end
+
+      it "gives qualified names with their versions" do
+        expect(fq_names).to eq([ "apt::default@~> 1.2.0" ])
+      end
+
+      it "does not mutate the recipe name" do
+        expect(fq_names).to eq([ "apt::default@~> 1.2.0" ])
+        expect(list).to eq( [ "apt" ] )
+      end
+
+    end
+
+    context "with fully qualified names that have version constraints" do
+
+      let(:versioned_recipes) do
+        [
+          {:name => "apt::cacher", :version => "~> 1.2.0"},
+        ]
+      end
+
+      it "gives qualified names with their versions" do
+        expect(fq_names).to eq([ "apt::cacher@~> 1.2.0" ])
+      end
+
+      it "does not mutate the recipe name" do
+        expect(fq_names).to eq([ "apt::cacher@~> 1.2.0" ])
+        expect(list).to eq( [ "apt::cacher" ] )
+      end
+
+    end
+  end
+
+  context "with duplicated names", :chef_gte_13_only do
+    it "should fail in Chef 13" do
+      expect(list).to_not respond_to(:with_duplicate_names)
+    end
+  end
 end
diff --git a/spec/unit/run_list_spec.rb b/spec/unit/run_list_spec.rb
index bf996de..e150579 100644
--- a/spec/unit/run_list_spec.rb
+++ b/spec/unit/run_list_spec.rb
@@ -307,7 +307,7 @@ describe Chef::RunList do
       expect(Chef::JSONCompat.to_json(@run_list)).to eq(Chef::JSONCompat.to_json(["recipe[nagios::client]", "role[production]", "recipe[apache2]"]))
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) { @run_list }
     end
 
diff --git a/spec/unit/search/query_spec.rb b/spec/unit/search/query_spec.rb
index 59ac80f..f85b176 100644
--- a/spec/unit/search/query_spec.rb
+++ b/spec/unit/search/query_spec.rb
@@ -83,6 +83,8 @@ describe Chef::Search::Query do
   describe "search" do
     let(:query_string) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0" }
     let(:query_string_continue) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=4" }
+    let(:query_string_with_rows) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=4" }
+    let(:query_string_continue_with_rows) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=4&rows=4" }
 
     let(:response) { {
       "rows" => [
@@ -149,6 +151,14 @@ describe Chef::Search::Query do
       r
     }
 
+    let(:big_response_empty) {
+      {
+        "start" => 0,
+        "total" => 8,
+        "rows" => []
+      }
+    }
+
     let(:big_response_end) {
       r = response.dup
       r["start"] = 4
@@ -208,7 +218,7 @@ describe Chef::Search::Query do
     it "pages through the responses" do
       @call_me = double("blocky")
       response["rows"].each { |r| expect(@call_me).to receive(:do).with(r) }
-      query.search(:node, "*:*", sort: nil, start: 0, rows: 1) { |r| @call_me.do(r) }
+      query.search(:node, "*:*", sort: nil, start: 0, rows: 4) { |r| @call_me.do(r) }
     end
 
     it "sends multiple API requests when the server indicates there is more data" do
@@ -219,6 +229,14 @@ describe Chef::Search::Query do
       end
     end
 
+    it "paginates correctly in the face of filtered nodes" do
+      expect(rest).to receive(:get_rest).with(query_string_with_rows).and_return(big_response_empty)
+      expect(rest).to receive(:get_rest).with(query_string_continue_with_rows).and_return(big_response_end)
+      query.search(:node, "platform:rhel", rows: 4) do |r|
+        nil
+      end
+    end
+
     context "when :filter_result is provided as a result" do
       include_context "filtered search" do
         let(:filter_key) { :filter_result }
diff --git a/spec/unit/shell_spec.rb b/spec/unit/shell_spec.rb
index acbb189..379043a 100644
--- a/spec/unit/shell_spec.rb
+++ b/spec/unit/shell_spec.rb
@@ -43,7 +43,7 @@ describe Shell do
   before do
     Shell.irb_conf = {}
     allow(Shell::ShellSession.instance).to receive(:reset!)
-    allow(Chef::Platform).to receive(:windows?).and_return(false)
+    allow(ChefConfig).to receive(:windows?).and_return(false)
     allow(Chef::Util::PathHelper).to receive(:home).and_return('/home/foo')
   end
 
@@ -71,7 +71,7 @@ describe Shell do
       Shell.irb_conf[:IRB_RC].call(conf)
       expect(conf.prompt_c).to      eq("chef > ")
       expect(conf.return_format).to eq(" => %s \n")
-      expect(conf.prompt_i).to      eq("chef > ")
+      expect(conf.prompt_i).to      eq("chef (#{Chef::VERSION})> ")
       expect(conf.prompt_n).to      eq("chef ?> ")
       expect(conf.prompt_s).to      eq("chef%l> ")
       expect(conf.use_tracer).to    eq(false)
@@ -85,7 +85,7 @@ describe Shell do
       conf.main = Chef::Recipe.new(nil,nil,Chef::RunContext.new(Chef::Node.new, {}, events))
       Shell.irb_conf[:IRB_RC].call(conf)
       expect(conf.prompt_c).to      eq("chef:recipe > ")
-      expect(conf.prompt_i).to      eq("chef:recipe > ")
+      expect(conf.prompt_i).to      eq("chef:recipe (#{Chef::VERSION})> ")
       expect(conf.prompt_n).to      eq("chef:recipe ?> ")
       expect(conf.prompt_s).to      eq("chef:recipe%l> ")
     end
@@ -97,7 +97,7 @@ describe Shell do
       conf.main = Chef::Node.new
       Shell.irb_conf[:IRB_RC].call(conf)
       expect(conf.prompt_c).to      eq("chef:attributes > ")
-      expect(conf.prompt_i).to      eq("chef:attributes > ")
+      expect(conf.prompt_i).to      eq("chef:attributes (#{Chef::VERSION})> ")
       expect(conf.prompt_n).to      eq("chef:attributes ?> ")
       expect(conf.prompt_s).to      eq("chef:attributes%l> ")
     end
diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb
index d451531..97cc32e 100644
--- a/spec/unit/user_spec.rb
+++ b/spec/unit/user_spec.rb
@@ -16,6 +16,11 @@
 # limitations under the License.
 #
 
+# DEPRECATION NOTE
+# This code only remains to support users still	operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_spec.rb.
+
 require 'spec_helper'
 
 require 'chef/user'
@@ -155,7 +160,7 @@ describe Chef::User do
       expect(@json).not_to include("password")
     end
 
-    include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
       let(:jsonable) { @user }
     end
   end
@@ -200,8 +205,8 @@ describe Chef::User do
     before (:each) do
       @user = Chef::User.new
       @user.name "foobar"
-      @http_client = double("Chef::REST mock")
-      allow(Chef::REST).to receive(:new).and_return(@http_client)
+      @http_client = double("Chef::ServerAPI mock")
+      allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
     end
 
     describe "list" do
@@ -214,24 +219,24 @@ describe Chef::User do
       end
 
       it "lists all clients on an OSC server" do
-        allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response)
+        allow(@http_client).to receive(:get).with("users").and_return(@osc_response)
         expect(Chef::User.list).to eq(@osc_response)
       end
 
       it "inflate all clients on an OSC server" do
-        allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response)
+        allow(@http_client).to receive(:get).with("users").and_return(@osc_response)
         expect(Chef::User.list(true)).to eq(@osc_inflated_response)
       end
 
       it "lists all clients on an OHC/OPC server" do
-        allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response)
+        allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
         # We expect that Chef::User.list will give a consistent response
         # so OHC API responses should be transformed to OSC-style output.
         expect(Chef::User.list).to eq(@osc_response)
       end
 
       it "inflate all clients on an OHC/OPC server" do
-        allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response)
+        allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
         expect(Chef::User.list(true)).to eq(@osc_inflated_response)
       end
     end
@@ -239,14 +244,14 @@ describe Chef::User do
     describe "create" do
       it "creates a new user via the API" do
         @user.password "password"
-        expect(@http_client).to receive(:post_rest).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({})
+        expect(@http_client).to receive(:post).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({})
         @user.create
       end
     end
 
     describe "read" do
       it "loads a named user from the API" do
-        expect(@http_client).to receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"})
+        expect(@http_client).to receive(:get).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"})
         user = Chef::User.load("foobar")
         expect(user.name).to eq("foobar")
         expect(user.admin).to eq(true)
@@ -256,14 +261,14 @@ describe Chef::User do
 
     describe "update" do
       it "updates an existing user on via the API" do
-        expect(@http_client).to receive(:put_rest).with("users/foobar", {:name => "foobar", :admin => false}).and_return({})
+        expect(@http_client).to receive(:put).with("users/foobar", {:name => "foobar", :admin => false}).and_return({})
         @user.update
       end
     end
 
     describe "destroy" do
       it "deletes the specified user via the API" do
-        expect(@http_client).to receive(:delete_rest).with("users/foobar")
+        expect(@http_client).to receive(:delete).with("users/foobar")
         @user.destroy
       end
     end
diff --git a/spec/unit/user_v1_spec.rb b/spec/unit/user_v1_spec.rb
new file mode 100644
index 0000000..8fd370a
--- /dev/null
+++ b/spec/unit/user_v1_spec.rb
@@ -0,0 +1,584 @@
+#
+# Author:: Steven Danna (steve at opscode.com)
+# Copyright:: Copyright (c) 2012 Opscode, 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 'spec_helper'
+
+require 'chef/user_v1'
+require 'tempfile'
+
+describe Chef::UserV1 do
+  before(:each) do
+    @user = Chef::UserV1.new
+  end
+
+  shared_examples_for "string fields with no contraints" do
+    it "should let you set the public key" do
+      expect(@user.send(method, "some_string")).to eq("some_string")
+    end
+
+    it "should return the current public key" do
+      @user.send(method, "some_string")
+      expect(@user.send(method)).to eq("some_string")
+    end
+
+    it "should throw an ArgumentError if you feed it something lame" do
+      expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError)
+    end
+  end
+
+  shared_examples_for "boolean fields with no constraints" do
+    it "should let you set the field" do
+      expect(@user.send(method, true)).to eq(true)
+    end
+
+    it "should return the current field value" do
+      @user.send(method, true)
+      expect(@user.send(method)).to eq(true)
+    end
+
+    it "should return the false value when false" do
+      @user.send(method, false)
+      expect(@user.send(method)).to eq(false)
+    end
+
+    it "should throw an ArgumentError if you feed it anything but true or false" do
+      expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError)
+    end
+  end
+
+  describe "initialize" do
+    it "should be a Chef::UserV1" do
+      expect(@user).to be_a_kind_of(Chef::UserV1)
+    end
+  end
+
+  describe "username" do
+    it "should let you set the username to a string" do
+      expect(@user.username("ops_master")).to eq("ops_master")
+    end
+
+    it "should return the current username" do
+      @user.username "ops_master"
+      expect(@user.username).to eq("ops_master")
+    end
+
+    # It is not feasible to check all invalid characters.  Here are a few
+    # that we probably care about.
+    it "should not accept invalid characters" do
+      # capital letters
+      expect { @user.username "Bar" }.to raise_error(ArgumentError)
+      # slashes
+      expect { @user.username "foo/bar" }.to raise_error(ArgumentError)
+      # ?
+      expect { @user.username "foo?" }.to raise_error(ArgumentError)
+      # &
+      expect { @user.username "foo&" }.to raise_error(ArgumentError)
+    end
+
+
+    it "should not accept spaces" do
+      expect { @user.username "ops master" }.to raise_error(ArgumentError)
+    end
+
+    it "should throw an ArgumentError if you feed it anything but a string" do
+      expect { @user.username Hash.new }.to raise_error(ArgumentError)
+    end
+  end
+
+  describe "boolean fields" do
+    describe "create_key" do
+      it_should_behave_like "boolean fields with no constraints" do
+        let(:method) { :create_key }
+      end
+    end
+  end
+
+  describe "string fields" do
+    describe "public_key" do
+      it_should_behave_like "string fields with no contraints" do
+        let(:method) { :public_key }
+      end
+    end
+
+    describe "private_key" do
+      it_should_behave_like "string fields with no contraints" do
+        let(:method) { :private_key }
+      end
+    end
+
+    describe "display_name" do
+      it_should_behave_like "string fields with no contraints" do
+        let(:method) { :display_name }
+      end
+    end
+
+    describe "first_name" do
+      it_should_behave_like "string fields with no contraints" do
+        let(:method) { :first_name }
+      end
+    end
+
+    describe "middle_name" do
+      it_should_behave_like "string fields with no contraints" do
+        let(:method) { :middle_name }
+      end
+    end
+
+    describe "last_name" do
+      it_should_behave_like "string fields with no contraints" do
+        let(:method) { :last_name }
+      end
+    end
+
+    describe "email" do
+      it_should_behave_like "string fields with no contraints" do
+        let(:method) { :email }
+      end
+    end
+
+    describe "password" do
+      it_should_behave_like "string fields with no contraints" do
+        let(:method) { :password }
+      end
+    end
+  end
+
+  describe "when serializing to JSON" do
+    before(:each) do
+      @user.username("black")
+      @json = @user.to_json
+    end
+
+    it "serializes as a JSON object" do
+      expect(@json).to match(/^\{.+\}$/)
+    end
+
+    it "includes the username value" do
+      expect(@json).to include(%q{"username":"black"})
+    end
+
+    it "includes the display name when present" do
+      @user.display_name("get_displayed")
+      expect(@user.to_json).to include(%{"display_name":"get_displayed"})
+    end
+
+    it "does not include the display name if not present" do
+      expect(@json).not_to include("display_name")
+    end
+
+    it "includes the first name when present" do
+      @user.first_name("char")
+      expect(@user.to_json).to include(%{"first_name":"char"})
+    end
+
+    it "does not include the first name if not present" do
+      expect(@json).not_to include("first_name")
+    end
+
+    it "includes the middle name when present" do
+      @user.middle_name("man")
+      expect(@user.to_json).to include(%{"middle_name":"man"})
+    end
+
+    it "does not include the middle name if not present" do
+      expect(@json).not_to include("middle_name")
+    end
+
+    it "includes the last name when present" do
+      @user.last_name("der")
+      expect(@user.to_json).to include(%{"last_name":"der"})
+    end
+
+    it "does not include the last name if not present" do
+      expect(@json).not_to include("last_name")
+    end
+
+    it "includes the email when present" do
+      @user.email("charmander at pokemon.poke")
+      expect(@user.to_json).to include(%{"email":"charmander at pokemon.poke"})
+    end
+
+    it "does not include the email if not present" do
+      expect(@json).not_to include("email")
+    end
+
+    it "includes the public key when present" do
+      @user.public_key("crowes")
+      expect(@user.to_json).to include(%{"public_key":"crowes"})
+    end
+
+    it "does not include the public key if not present" do
+      expect(@json).not_to include("public_key")
+    end
+
+    it "includes the private key when present" do
+      @user.private_key("monkeypants")
+      expect(@user.to_json).to include(%q{"private_key":"monkeypants"})
+    end
+
+    it "does not include the private key if not present" do
+      expect(@json).not_to include("private_key")
+    end
+
+    it "includes the password if present" do
+      @user.password "password"
+      expect(@user.to_json).to include(%q{"password":"password"})
+    end
+
+    it "does not include the password if not present" do
+      expect(@json).not_to include("password")
+    end
+
+    include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
+      let(:jsonable) { @user }
+    end
+  end
+
+  describe "when deserializing from JSON" do
+    before(:each) do
+      user = {
+        "username" => "mr_spinks",
+        "display_name" => "displayed",
+        "first_name" => "char",
+        "middle_name" => "man",
+        "last_name" => "der",
+        "email" => "charmander at pokemon.poke",
+        "password" => "password",
+        "public_key" => "turtles",
+        "private_key" => "pandas",
+        "create_key" => false
+      }
+      @user = Chef::UserV1.from_json(Chef::JSONCompat.to_json(user))
+    end
+
+    it "should deserialize to a Chef::UserV1 object" do
+      expect(@user).to be_a_kind_of(Chef::UserV1)
+    end
+
+    it "preserves the username" do
+      expect(@user.username).to eq("mr_spinks")
+    end
+
+    it "preserves the display name if present" do
+      expect(@user.display_name).to eq("displayed")
+    end
+
+    it "preserves the first name if present" do
+      expect(@user.first_name).to eq("char")
+    end
+
+    it "preserves the middle name if present" do
+      expect(@user.middle_name).to eq("man")
+    end
+
+    it "preserves the last name if present" do
+      expect(@user.last_name).to eq("der")
+    end
+
+    it "preserves the email if present" do
+      expect(@user.email).to eq("charmander at pokemon.poke")
+    end
+
+    it "includes the password if present" do
+      expect(@user.password).to eq("password")
+    end
+
+    it "preserves the public key if present" do
+      expect(@user.public_key).to eq("turtles")
+    end
+
+    it "includes the private key if present" do
+      expect(@user.private_key).to eq("pandas")
+    end
+
+    it "includes the create key status if not nil" do
+      expect(@user.create_key).to be_falsey
+    end
+  end
+
+  describe "Versioned API Interactions" do
+    let(:response_406) { OpenStruct.new(:code => '406') }
+    let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+
+    before (:each) do
+      @user = Chef::UserV1.new
+      allow(@user).to receive(:chef_root_rest_v0).and_return(double('chef rest root v0 object'))
+      allow(@user).to receive(:chef_root_rest_v1).and_return(double('chef rest root v1 object'))
+    end
+
+    describe "update" do
+      before do
+        # populate all fields that are valid between V0 and V1
+        @user.username "some_username"
+        @user.display_name "some_display_name"
+        @user.first_name "some_first_name"
+        @user.middle_name "some_middle_name"
+        @user.last_name "some_last_name"
+        @user.email "some_email"
+        @user.password "some_password"
+      end
+
+      let(:payload) {
+        {
+          :username => "some_username",
+          :display_name => "some_display_name",
+          :first_name => "some_first_name",
+          :middle_name => "some_middle_name",
+          :last_name => "some_last_name",
+          :email => "some_email",
+          :password => "some_password"
+        }
+      }
+
+      context "when server API V1 is valid on the Chef Server receiving the request" do
+        context "when the user submits valid data" do
+          it "properly updates the user" do
+            expect(@user.chef_root_rest_v1).to receive(:put).with("users/some_username", payload).and_return({})
+            @user.update
+          end
+        end
+      end
+
+      context "when server API V1 is not valid on the Chef Server receiving the request" do
+        let(:payload) {
+          {
+            :username => "some_username",
+            :display_name => "some_display_name",
+            :first_name => "some_first_name",
+            :middle_name => "some_middle_name",
+            :last_name => "some_last_name",
+            :email => "some_email",
+            :password => "some_password",
+            :public_key => "some_public_key"
+          }
+        }
+
+        before do
+          @user.public_key "some_public_key"
+          allow(@user.chef_root_rest_v1).to receive(:put)
+        end
+
+        context "when the server returns a 400" do
+          let(:response_400) { OpenStruct.new(:code => '400') }
+          let(:exception_400) { Net::HTTPServerException.new("400 Bad Request", response_400) }
+
+          context "when the 400 was due to public / private key fields no longer being supported" do
+            let(:response_body_400) { '{"error":["Since Server API v1, all keys must be updated via the keys endpoint. "]}' }
+
+            before do
+              allow(response_400).to receive(:body).and_return(response_body_400)
+              allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400)
+            end
+
+            it "proceeds with the V0 PUT since it can handle public / private key fields" do
+              expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({})
+              @user.update
+            end
+
+            it "does not call server_client_api_version_intersection, since we know to proceed with V0 in this case" do
+              expect(@user).to_not receive(:server_client_api_version_intersection)
+              allow(@user.chef_root_rest_v0).to receive(:put).and_return({})
+              @user.update
+            end
+          end # when the 400 was due to public / private key fields
+
+          context "when the 400 was NOT due to public / private key fields no longer being supported" do
+            let(:response_body_400) { '{"error":["Some other error. "]}' }
+
+            before do
+              allow(response_400).to receive(:body).and_return(response_body_400)
+              allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400)
+            end
+
+            it "will not proceed with the V0 PUT since the original bad request was not key related" do
+              expect(@user.chef_root_rest_v0).to_not receive(:put).with("users/some_username", payload)
+              expect { @user.update }.to raise_error(exception_400)
+            end
+
+            it "raises the original error" do
+              expect { @user.update }.to raise_error(exception_400)
+            end
+
+          end
+        end # when the server returns a 400
+
+        context "when the server returns a 406" do
+          # from spec/support/shared/unit/api_versioning.rb
+          it_should_behave_like "version handling" do
+            let(:object)    { @user }
+            let(:method)    { :update }
+            let(:http_verb) { :put }
+            let(:rest_v1)   { @user.chef_root_rest_v1 }
+          end
+
+          context "when the server supports API V0" do
+            before do
+              allow(@user).to receive(:server_client_api_version_intersection).and_return([0])
+              allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_406)
+            end
+
+            it "properly updates the user" do
+              expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({})
+              @user.update
+            end
+          end # when the server supports API V0
+        end # when the server returns a 406
+
+      end # when server API V1 is not valid on the Chef Server receiving the request
+    end # update
+
+    describe "create" do
+      let(:payload) {
+        {
+          :username => "some_username",
+          :display_name => "some_display_name",
+          :first_name => "some_first_name",
+          :last_name => "some_last_name",
+          :email => "some_email",
+          :password => "some_password"
+        }
+      }
+      before do
+        @user.username "some_username"
+        @user.display_name "some_display_name"
+        @user.first_name "some_first_name"
+        @user.last_name "some_last_name"
+        @user.email "some_email"
+        @user.password "some_password"
+      end
+
+      # from spec/support/shared/unit/user_and_client_shared.rb
+      it_should_behave_like "user or client create" do
+        let(:object)  { @user }
+        let(:error)   { Chef::Exceptions::InvalidUserAttribute }
+        let(:rest_v0) { @user.chef_root_rest_v0 }
+        let(:rest_v1) { @user.chef_root_rest_v1 }
+        let(:url)     { "users" }
+      end
+
+      context "when handling API V1" do
+        it "creates a new user via the API with a middle_name when it exists" do
+          @user.middle_name "some_middle_name"
+          expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({})
+          @user.create
+        end
+      end # when server API V1 is valid on the Chef Server receiving the request
+
+      context "when API V1 is not supported by the server" do
+        # from spec/support/shared/unit/api_versioning.rb
+        it_should_behave_like "version handling" do
+          let(:object)    { @user }
+          let(:method)    { :create }
+          let(:http_verb) { :post }
+          let(:rest_v1)   { @user.chef_root_rest_v1 }
+        end
+      end
+
+      context "when handling API V0" do
+        before do
+          allow(@user).to receive(:server_client_api_version_intersection).and_return([0])
+          allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406)
+        end
+
+        it "creates a new user via the API with a middle_name when it exists" do
+          @user.middle_name "some_middle_name"
+          expect(@user.chef_root_rest_v0).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({})
+          @user.create
+        end
+      end # when server API V1 is not valid on the Chef Server receiving the request
+
+    end # create
+
+    # DEPRECATION
+    # This can be removed after API V0 support is gone
+    describe "reregister" do
+      let(:payload) {
+        {
+          "username" => "some_username",
+        }
+      }
+
+      before do
+        @user.username "some_username"
+      end
+
+      context "when server API V0 is valid on the Chef Server receiving the request" do
+        it "creates a new object via the API" do
+          expect(@user.chef_root_rest_v0).to receive(:put).with("users/#{@user.username}", payload.merge({"private_key" => true})).and_return({})
+          @user.reregister
+        end
+      end # when server API V0 is valid on the Chef Server receiving the request
+
+      context "when server API V0 is not supported by the Chef Server" do
+        # from spec/support/shared/unit/api_versioning.rb
+        it_should_behave_like "user and client reregister" do
+          let(:object)    { @user }
+          let(:rest_v0)   { @user.chef_root_rest_v0 }
+        end
+      end # when server API V0 is not supported by the Chef Server
+    end # reregister
+
+  end # Versioned API Interactions
+
+  describe "API Interactions" do
+    before (:each) do
+      @user = Chef::UserV1.new
+      @user.username "foobar"
+      @http_client = double("Chef::REST mock")
+      allow(Chef::REST).to receive(:new).and_return(@http_client)
+    end
+
+    describe "list" do
+      before(:each) do
+        Chef::Config[:chef_server_url] = "http://www.example.com"
+        @osc_response = { "admin" => "http://www.example.com/users/admin"}
+        @ohc_response = [ { "user" => { "username" => "admin" }} ]
+        allow(Chef::UserV1).to receive(:load).with("admin").and_return(@user)
+        @osc_inflated_response = { "admin" => @user }
+      end
+
+      it "lists all clients on an OHC/OPC server" do
+        allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
+        # We expect that Chef::UserV1.list will give a consistent response
+        # so OHC API responses should be transformed to OSC-style output.
+        expect(Chef::UserV1.list).to eq(@osc_response)
+      end
+
+      it "inflate all clients on an OHC/OPC server" do
+        allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
+        expect(Chef::UserV1.list(true)).to eq(@osc_inflated_response)
+      end
+    end
+
+    describe "read" do
+      it "loads a named user from the API" do
+        expect(@http_client).to receive(:get).with("users/foobar").and_return({"username" => "foobar", "admin" => true, "public_key" => "pubkey"})
+        user = Chef::UserV1.load("foobar")
+        expect(user.username).to eq("foobar")
+        expect(user.public_key).to eq("pubkey")
+      end
+    end
+
+    describe "destroy" do
+      it "deletes the specified user via the API" do
+        expect(@http_client).to receive(:delete).with("users/foobar")
+        @user.destroy
+      end
+    end
+  end
+end
diff --git a/spec/unit/util/path_helper_spec.rb b/spec/unit/util/path_helper_spec.rb
deleted file mode 100644
index 23db958..0000000
--- a/spec/unit/util/path_helper_spec.rb
+++ /dev/null
@@ -1,255 +0,0 @@
-#
-# Author:: Bryan McLellan <btm at loftninjas.org>
-# Copyright:: Copyright (c) 2014 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/util/path_helper'
-require 'spec_helper'
-
-describe Chef::Util::PathHelper do
-  PathHelper = Chef::Util::PathHelper
-
-  [ false, true ].each do |is_windows|
-    context "on #{is_windows ? "windows" : "unix"}" do
-      before(:each) do
-        allow(Chef::Platform).to receive(:windows?).and_return(is_windows)
-      end
-
-      describe "join" do
-        it "joins components when some end with separators" do
-          expected = PathHelper.cleanpath("/foo/bar/baz")
-          expected = "C:#{expected}" if is_windows
-          expect(PathHelper.join(is_windows ? 'C:\\foo\\' : "/foo/", "bar", "baz")).to eq(expected)
-        end
-
-        it "joins components when some end and start with separators" do
-          expected = PathHelper.cleanpath("/foo/bar/baz")
-          expected = "C:#{expected}" if is_windows
-          expect(PathHelper.join(is_windows ? 'C:\\foo\\' : "/foo/", "bar/", "/baz")).to eq(expected)
-        end
-
-        it "joins components that don't end in separators" do
-          expected = PathHelper.cleanpath("/foo/bar/baz")
-          expected = "C:#{expected}" if is_windows
-          expect(PathHelper.join(is_windows ? 'C:\\foo' : "/foo", "bar", "baz")).to eq(expected)
-        end
-
-        it "joins starting with '' resolve to absolute paths" do
-          expect(PathHelper.join('', 'a', 'b')).to eq("#{PathHelper.path_separator}a#{PathHelper.path_separator}b")
-        end
-
-        it "joins ending with '' add a / to the end" do
-          expect(PathHelper.join('a', 'b', '')).to eq("a#{PathHelper.path_separator}b#{PathHelper.path_separator}")
-        end
-
-        if is_windows
-          it "joins components on Windows when some end with unix separators" do
-            expect(PathHelper.join('C:\\foo/', "bar", "baz")).to eq('C:\\foo\\bar\\baz')
-          end
-        end
-      end
-
-      if is_windows
-        it "path_separator is \\" do
-          expect(PathHelper.path_separator).to eq('\\')
-        end
-      else
-        it "path_separator is /" do
-          expect(PathHelper.path_separator).to eq('/')
-        end
-      end
-
-      if is_windows
-        it "cleanpath changes slashes into backslashes and leaves backslashes alone" do
-          expect(PathHelper.cleanpath('/a/b\\c/d/')).to eq('\\a\\b\\c\\d')
-        end
-        it "cleanpath does not remove leading double backslash" do
-          expect(PathHelper.cleanpath('\\\\a/b\\c/d/')).to eq('\\\\a\\b\\c\\d')
-        end
-      else
-        it "cleanpath removes extra slashes alone" do
-          expect(PathHelper.cleanpath('/a///b/c/d/')).to eq('/a/b/c/d')
-        end
-      end
-
-      describe "dirname" do
-        it "dirname('abc') is '.'" do
-          expect(PathHelper.dirname('abc')).to eq('.')
-        end
-        it "dirname('/') is '/'" do
-          expect(PathHelper.dirname(PathHelper.path_separator)).to eq(PathHelper.path_separator)
-        end
-        it "dirname('a/b/c') is 'a/b'" do
-          expect(PathHelper.dirname(PathHelper.join('a', 'b', 'c'))).to eq(PathHelper.join('a', 'b'))
-        end
-        it "dirname('a/b/c/') is 'a/b'" do
-          expect(PathHelper.dirname(PathHelper.join('a', 'b', 'c', ''))).to eq(PathHelper.join('a', 'b'))
-        end
-        it "dirname('/a/b/c') is '/a/b'" do
-          expect(PathHelper.dirname(PathHelper.join('', 'a', 'b', 'c'))).to eq(PathHelper.join('', 'a', 'b'))
-        end
-      end
-    end
-  end
-
-  describe "validate_path" do
-    context "on windows" do
-      before(:each) do
-        # pass by default
-        allow(Chef::Platform).to receive(:windows?).and_return(true)
-        allow(PathHelper).to receive(:printable?).and_return(true)
-        allow(PathHelper).to receive(:windows_max_length_exceeded?).and_return(false)
-      end
-
-      it "returns the path if the path passes the tests" do
-        expect(PathHelper.validate_path("C:\\ThisIsRigged")).to eql("C:\\ThisIsRigged")
-      end
-
-      it "does not raise an error if everything looks great" do
-        expect { PathHelper.validate_path("C:\\cool path\\dude.exe") }.not_to raise_error
-      end
-
-      it "raises an error if the path has invalid characters" do
-        allow(PathHelper).to receive(:printable?).and_return(false)
-        expect { PathHelper.validate_path("Newline!\n") }.to raise_error(Chef::Exceptions::ValidationFailed)
-      end
-
-      it "Adds the \\\\?\\ prefix if the path exceeds MAX_LENGTH and does not have it" do
-        long_path = "C:\\" + "a" * 250 + "\\" + "b" * 250
-        prefixed_long_path = "\\\\?\\" + long_path
-        allow(PathHelper).to receive(:windows_max_length_exceeded?).and_return(true)
-        expect(PathHelper.validate_path(long_path)).to eql(prefixed_long_path)
-      end
-    end
-  end
-
-  describe "windows_max_length_exceeded?" do
-    it "returns true if the path is too long (259 + NUL) for the API" do
-      expect(PathHelper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 6)).to be_truthy
-    end
-
-    it "returns false if the path is not too long (259 + NUL) for the standard API" do
-      expect(PathHelper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 5)).to be_falsey
-    end
-
-    it "returns false if the path is over 259 characters but uses the \\\\?\\ prefix" do
-      expect(PathHelper.windows_max_length_exceeded?("\\\\?\\C:\\" + "a" * 250 + "\\" + "b" * 250)).to be_falsey
-    end
-  end
-
-  describe "printable?" do
-    it "returns true if the string contains no non-printable characters" do
-      expect(PathHelper.printable?("C:\\Program Files (x86)\\Microsoft Office\\Files.lst")).to be_truthy
-    end
-
-    it "returns true when given 'abc' in unicode" do
-      expect(PathHelper.printable?("\u0061\u0062\u0063")).to be_truthy
-    end
-
-    it "returns true when given japanese unicode" do
-      expect(PathHelper.printable?("\uff86\uff87\uff88")).to be_truthy
-    end
-
-    it "returns false if the string contains a non-printable character" do
-      expect(PathHelper.printable?("\my files\work\notes.txt")).to be_falsey
-    end
-
-    # This isn't necessarily a requirement, but here to be explicit about functionality.
-    it "returns false if the string contains a newline or tab" do
-      expect(PathHelper.printable?("\tThere's no way,\n\t *no* way,\n\t that you came from my loins.\n")).to be_falsey
-    end
-  end
-
-  describe "canonical_path" do
-    context "on windows", :windows_only do
-      it "returns an absolute path with backslashes instead of slashes" do
-        expect(PathHelper.canonical_path("\\\\?\\C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini")
-      end
-
-      it "adds the \\\\?\\ prefix if it is missing" do
-        expect(PathHelper.canonical_path("C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini")
-      end
-
-      it "returns a lowercase path" do
-        expect(PathHelper.canonical_path("\\\\?\\C:\\CASE\\INSENSITIVE")).to eq("\\\\?\\c:\\case\\insensitive")
-      end
-    end
-
-    context "not on windows", :unix_only  do
-      it "returns a canonical path" do
-        expect(PathHelper.canonical_path("/etc//apache.d/sites-enabled/../sites-available/default")).to eq("/etc/apache.d/sites-available/default")
-      end
-    end
-  end
-
-  describe "paths_eql?" do
-    it "returns true if the paths are the same" do
-      allow(PathHelper).to receive(:canonical_path).with("bandit").and_return("c:/bandit/bandit")
-      allow(PathHelper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit")
-      expect(PathHelper.paths_eql?("bandit", "../bandit/bandit")).to be_truthy
-    end
-
-    it "returns false if the paths are different" do
-      allow(PathHelper).to receive(:canonical_path).with("bandit").and_return("c:/Bo/Bandit")
-      allow(PathHelper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit")
-      expect(PathHelper.paths_eql?("bandit", "../bandit/bandit")).to be_falsey
-    end
-  end
-
-  describe "escape_glob" do
-    it "escapes characters reserved by glob" do
-      path = "C:\\this\\*path\\[needs]\\escaping?"
-      escaped_path = "C:\\\\this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?"
-      expect(PathHelper.escape_glob(path)).to eq(escaped_path)
-    end
-
-    context "when given more than one argument" do
-      it "joins, cleanpaths, and escapes characters reserved by glob" do
-        args = ["this/*path", "[needs]", "escaping?"]
-        escaped_path = if windows?
-          "this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?"
-        else
-          "this/\\*path/\\[needs\\]/escaping\\?"
-        end
-        expect(PathHelper).to receive(:join).with(*args).and_call_original
-        expect(PathHelper).to receive(:cleanpath).and_call_original
-        expect(PathHelper.escape_glob(*args)).to eq(escaped_path)
-      end
-    end
-  end
-
-  describe "all_homes" do
-    before do
-      stub_const('ENV', env)
-      allow(Chef::Platform).to receive(:windows?).and_return(is_windows)
-    end
-
-    context "on windows" do
-      let (:is_windows) { true }
-    end
-
-    context "on unix" do
-      let (:is_windows) { false }
-
-      context "when HOME is not set" do
-        let (:env) { {} }
-        it "returns an empty array" do
-          expect(PathHelper.all_homes).to eq([])
-        end
-      end
-    end
-  end
-end
diff --git a/spec/unit/util/powershell/ps_credential_spec.rb b/spec/unit/util/powershell/ps_credential_spec.rb
index bac58b0..668ec52 100644
--- a/spec/unit/util/powershell/ps_credential_spec.rb
+++ b/spec/unit/util/powershell/ps_credential_spec.rb
@@ -21,7 +21,7 @@ require 'chef/util/powershell/ps_credential'
 
 describe Chef::Util::Powershell::PSCredential do
   let (:username) { 'foo' }
-  let (:password) { 'password' }
+  let (:password) { 'ThIsIsThEpAsSwOrD' }
 
   context 'when username and password are provided' do
     let(:ps_credential) { Chef::Util::Powershell::PSCredential.new(username, password)}
@@ -33,5 +33,12 @@ describe Chef::Util::Powershell::PSCredential do
             "'#{username}',('encrypted' | ConvertTo-SecureString))")
       end
     end
+
+    context 'when to_text is called' do
+      it 'should not contain the password' do
+        allow(ps_credential).to receive(:encrypt).with(password).and_return('encrypted')
+        expect(ps_credential.to_text).not_to match(/#{password}/)
+      end
+    end
   end
 end
diff --git a/spec/unit/win32/registry_spec.rb b/spec/unit/win32/registry_spec.rb
new file mode 100644
index 0000000..56def30
--- /dev/null
+++ b/spec/unit/win32/registry_spec.rb
@@ -0,0 +1,394 @@
+#
+# Author:: Prajakta Purohit (prajakta at opscode.com)
+# Copyright:: Copyright (c) 2012 Opscode, 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 'spec_helper'
+
+describe Chef::Win32::Registry do
+  include_context "Win32"
+
+  let(:value1) { { :name => "one", :type => :string, :data => "1" } }
+  let(:value1_upcase_name) { {:name => "ONE", :type => :string, :data => "1"} }
+  let(:key_path) { 'HKCU\Software\OpscodeNumbers' }
+  let(:key) { 'Software\OpscodeNumbers' }
+  let(:key_parent) { 'Software' }
+  let(:key_to_delete) { 'OpscodeNumbers' }
+  let(:sub_key) {'OpscodePrimes'}
+  let(:missing_key_path) {'HKCU\Software'}
+  let(:registry) { Chef::Win32::Registry.new() }
+  let(:hive_mock) { double("::Win32::Registry::KHKEY_CURRENT_USER") }
+  let(:reg_mock) { double("reg") }
+
+  before(:all) do
+    Win32::Registry = Class.new
+    Win32::Registry::Error = Class.new(RuntimeError)
+  end
+
+  before(:each) do
+    allow_any_instance_of(Chef::Win32::Registry).to receive(:machine_architecture).and_return(:x86_64)
+    
+    #Making the values for registry constants available on unix
+    Win32::Registry::KEY_SET_VALUE = 0x0002
+    Win32::Registry::KEY_QUERY_VALUE = 0x0001
+    Win32::Registry::KEY_WRITE = 0x00020000 | 0x0002 | 0x0004
+    Win32::Registry::KEY_READ = 0x00020000 | 0x0001 | 0x0008 | 0x0010
+  end
+
+  after(:each) do
+    Win32::Registry.send(:remove_const, 'KEY_SET_VALUE') if defined?(Win32::Registry::KEY_SET_VALUE)
+    Win32::Registry.send(:remove_const, 'KEY_QUERY_VALUE') if defined?(Win32::Registry::KEY_QUERY_VALUE)
+    Win32::Registry.send(:remove_const, 'KEY_READ') if defined?(Win32::Registry::KEY_READ)
+    Win32::Registry.send(:remove_const, 'KEY_WRITE') if defined?(Win32::Registry::KEY_WRITE)
+  end
+
+  describe "get_values" do
+    it "gets all values for a key if the key exists" do
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:map)
+      registry.get_values(key_path)
+    end
+
+    it "throws an exception if key does not exist" do
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      expect{registry.get_values(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+  end
+
+  describe "set_value" do
+    it "does nothing if key and hive and value exist" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
+      expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(true)
+      registry.set_value(key_path, value1)
+    end
+    it "does nothing if case insensitive key and hive and value exist" do
+      expect(registry).to receive(:key_exists!).with(key_path.downcase).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([hive_mock, key])
+      expect(registry).to receive(:value_exists?).with(key_path.downcase, value1).and_return(true)
+      expect(registry).to receive(:data_exists?).with(key_path.downcase, value1).and_return(true)
+      registry.set_value(key_path.downcase, value1)
+    end
+    it "does nothing if key and hive and value with a case insensitive name exist" do
+      expect(registry).to receive(:key_exists!).with(key_path.downcase).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([hive_mock, key])
+      expect(registry).to receive(:value_exists?).with(key_path.downcase, value1_upcase_name).and_return(true)
+      expect(registry).to receive(:data_exists?).with(key_path.downcase, value1_upcase_name).and_return(true)
+      registry.set_value(key_path.downcase, value1_upcase_name)
+    end
+    it "updates value if key and hive and value exist, but data is different" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
+      expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(false)
+      expect(hive_mock).to receive(:open).with(key, Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(registry).to receive(:get_type_from_name).with(:string).and_return(1)
+      expect(reg_mock).to receive(:write).with("one", 1, "1")
+      registry.set_value(key_path, value1)
+    end
+
+    it "creates value if the key exists and the value does not exist" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false)
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(registry).to receive(:get_type_from_name).with(:string).and_return(1)
+      expect(reg_mock).to receive(:write).with("one", 1, "1")
+      registry.set_value(key_path, value1)
+    end
+
+    it "should raise an exception if the key does not exist" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      expect {registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+  end
+
+  describe "delete_value" do
+    it "deletes value if value exists" do
+      expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:delete_value).with("one").and_return(true)
+      registry.delete_value(key_path, value1)
+    end
+
+    it "raises an exception if the key does not exist" do
+      expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      registry.delete_value(key_path, value1)
+    end
+
+    it "does nothing if the value does not exist" do
+      expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false)
+      registry.delete_value(key_path, value1)
+    end
+  end
+
+  describe "create_key" do
+    it "creates key if intermediate keys are missing and recursive is set to true" do
+      expect(registry).to receive(:keys_missing?).with(key_path).and_return(true)
+      expect(registry).to receive(:create_missing).with(key_path)
+      expect(registry).to receive(:key_exists?).with(key_path).and_return(false)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture)
+      registry.create_key(key_path, true)
+    end
+
+    it "raises an exception if intermediate keys are missing and recursive is set to false" do
+      expect(registry).to receive(:keys_missing?).with(key_path).and_return(true)
+      expect{registry.create_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive)
+    end
+
+    it "does nothing if the key exists" do
+      expect(registry).to receive(:keys_missing?).with(key_path).and_return(true)
+      expect(registry).to receive(:create_missing).with(key_path)
+      expect(registry).to receive(:key_exists?).with(key_path).and_return(true)
+      registry.create_key(key_path, true)
+    end
+
+    it "create key if intermediate keys not missing and recursive is set to false" do
+      expect(registry).to receive(:keys_missing?).with(key_path).and_return(false)
+      expect(registry).to receive(:key_exists?).with(key_path).and_return(false)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture)
+      registry.create_key(key_path, false)
+    end
+
+    it "create key if intermediate keys not missing and recursive is set to true" do
+      expect(registry).to receive(:keys_missing?).with(key_path).and_return(false)
+      expect(registry).to receive(:key_exists?).with(key_path).and_return(false)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture)
+      registry.create_key(key_path, true)
+    end
+  end
+
+  describe "delete_key", :windows_only do
+    it "deletes key if it has subkeys and recursive is set to true" do
+      expect(registry).to receive(:key_exists?).with(key_path).and_return(true)
+      expect(registry).to receive(:has_subkeys?).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true)
+      registry.delete_key(key_path, true)
+    end
+
+    it "raises an exception if it has subkeys but recursive is set to false" do
+      expect(registry).to receive(:key_exists?).with(key_path).and_return(true)
+      expect(registry).to receive(:has_subkeys?).with(key_path).and_return(true)
+      expect{registry.delete_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive)
+    end
+
+    it "deletes key if the key exists and has no subkeys" do
+      expect(registry).to receive(:key_exists?).with(key_path).and_return(true)
+      expect(registry).to receive(:has_subkeys?).with(key_path).and_return(false)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true)
+      registry.delete_key(key_path, true)
+    end
+  end
+
+  describe "key_exists?" do
+    it "returns true if key_exists" do
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(registry.key_exists?(key_path)).to eq(true)
+    end
+
+    it "returns false if key does not exist" do
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_raise(::Win32::Registry::Error)
+      expect(registry.key_exists?(key_path)).to eq(false)
+    end
+  end
+
+  describe "key_exists!" do
+    it "throws an exception if the key_parent does not exist" do
+      expect(registry).to receive(:key_exists?).with(key_path).and_return(false)
+      expect{registry.key_exists!(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+  end
+
+  describe "hive_exists?" do
+    it "returns true if the hive exists" do
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      registry.hive_exists?(key_path) == true
+    end
+
+    it "returns false if the hive does not exist" do
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegHiveMissing)
+      registry.hive_exists?(key_path) == false
+    end
+  end
+
+  describe "has_subkeys?" do
+    it "returns true if the key has subkeys" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:each_key).and_yield(key)
+      registry.has_subkeys?(key_path) == true
+    end
+
+    it "returns false if the key does not have subkeys" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:each_key).and_return(no_args())
+      expect(registry.has_subkeys?(key_path)).to eq(false)
+    end
+
+    it "throws an exception if the key does not exist" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      expect {registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+  end
+
+  describe "get_subkeys" do
+    it "returns the subkeys if they exist" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:each_key).and_yield(sub_key)
+      registry.get_subkeys(key_path)
+    end
+  end
+
+  describe "value_exists?" do
+    it "throws an exception if the key does not exist" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      expect {registry.value_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+
+    it "returns true if the value exists" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:any?).and_yield("one")
+      registry.value_exists?(key_path, value1) == true
+    end
+
+    it "returns false if the value does not exist" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:any?).and_yield(no_args())
+      registry.value_exists?(key_path, value1) == false
+    end
+  end
+
+  describe "data_exists?" do
+    it "throws an exception if the key does not exist" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      expect {registry.data_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+
+    it "returns true if the data exists" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(registry).to receive(:get_type_from_name).with(:string).and_return(1)
+      expect(reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "1")
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(registry.data_exists?(key_path, value1)).to eq(true)
+    end
+
+    it "returns false if the data does not exist" do
+      expect(registry).to receive(:key_exists!).with(key_path).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(registry).to receive(:get_type_from_name).with(:string).and_return(1)
+      expect(reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "2")
+      expect(registry.data_exists?(key_path, value1)).to eq(false)
+    end
+  end
+
+  describe "value_exists!" do
+    it "does nothing if the value exists" do
+      expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true)
+      registry.value_exists!(key_path, value1)
+    end
+
+    it "throws an exception if the value does not exist" do
+      expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false)
+      expect{registry.value_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing)
+    end
+  end
+
+  describe "data_exists!" do
+    it "does nothing if the data exists" do
+      expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(true)
+      registry.data_exists!(key_path, value1)
+    end
+
+    it "throws an exception if the data does not exist" do
+      expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(false)
+      expect{registry.data_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegDataMissing)
+    end
+  end
+
+  describe "type_matches?" do
+    it "returns true if type matches" do
+      expect(registry).to receive(:value_exists!).with(key_path, value1).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(registry).to receive(:get_type_from_name).with(:string).and_return(1)
+      expect(reg_mock).to receive(:each).and_yield("one", 1)
+      expect(registry.type_matches?(key_path, value1)).to eq(true)
+    end
+
+    it "returns false if type does not match" do
+      expect(registry).to receive(:value_exists!).with(key_path, value1).and_return(true)
+      expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key])
+      expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock)
+      expect(reg_mock).to receive(:each).and_yield("two", 2)
+      expect(registry.type_matches?(key_path, value1)).to eq(false)
+    end
+
+    it "throws an exception if value does not exist" do
+      expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false)
+      expect{registry.type_matches?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing)
+    end
+  end
+
+  describe "type_matches!" do
+    it "does nothing if the type_matches" do
+      expect(registry).to receive(:type_matches?).with(key_path, value1).and_return(true)
+      registry.type_matches!(key_path, value1)
+    end
+
+    it "throws an exception if the type does not match" do
+      expect(registry).to receive(:type_matches?).with(key_path, value1).and_return(false)
+      expect{registry.type_matches!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegTypesMismatch)
+    end
+  end
+
+  describe "keys_missing?" do
+    it "returns true if the keys are missing" do
+      expect(registry).to receive(:key_exists?).with(missing_key_path).and_return(false)
+      expect(registry.keys_missing?(key_path)).to eq(true)
+    end
+
+    it "returns false if no keys in the path are missing" do
+      expect(registry).to receive(:key_exists?).with(missing_key_path).and_return(true)
+      expect(registry.keys_missing?(key_path)).to eq(false)
+    end
+  end
+end
diff --git a/spec/unit/windows_service_spec.rb b/spec/unit/windows_service_spec.rb
index bc5e781..031a312 100644
--- a/spec/unit/windows_service_spec.rb
+++ b/spec/unit/windows_service_spec.rb
@@ -1,6 +1,6 @@
 #
 # Author:: Mukta Aphale (<mukta.aphale at clogeny.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# Copyright:: Copyright (c) 2013-2015 Chef Software, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,51 +21,96 @@ if Chef::Platform.windows?
 end
 
 describe "Chef::Application::WindowsService", :windows_only do
-  let (:instance) {Chef::Application::WindowsService.new}
-  let (:shell_out_result) {Object.new}
-  let (:tempfile) {Tempfile.new "log_file"}
+  let(:shell_out_result) { double('shellout', stdout: nil, stderr: nil) }
+  let(:config_options) do
+    {
+      log_location: STDOUT,
+      config_file: "test_config_file",
+      log_level: :info
+    }
+  end
+  let(:timeout) { 7200 }
+  let(:shellout_options) do
+    {
+      :timeout => timeout,
+      :logger => Chef::Log
+    }
+  end
+
   before do
-    allow(instance).to receive(:parse_options)
-    allow(shell_out_result).to receive(:stdout)
-    allow(shell_out_result).to receive(:stderr)
+    Chef::Config.merge!(config_options)
+    allow(subject).to receive(:configure_chef)
+    allow(subject).to receive(:parse_options)
+    allow(MonoLogger).to receive(:new)
+    allow(subject).to receive(:running?).and_return(true, false)
+    allow(subject).to receive(:state).and_return(4)
+    subject.service_init
   end
-  it "runs chef-client in new process" do
-    expect(instance).to receive(:configure_chef).twice
-    instance.service_init
-    expect(instance).to receive(:run_chef_client).and_call_original
-    expect(instance).to receive(:shell_out).and_return(shell_out_result)
-    allow(instance).to receive(:running?).and_return(true, false)
-    allow(instance.instance_variable_get(:@service_signal)).to receive(:wait)
-    allow(instance).to receive(:state).and_return(4)
-    instance.service_main
+
+  subject { Chef::Application::WindowsService.new }
+
+  it "passes DEFAULT_LOG_LOCATION to chef-client instead of STDOUT" do
+    expect(subject).to receive(:shell_out).with(
+      "chef-client  --no-fork -c test_config_file -L #{Chef::Application::WindowsService::DEFAULT_LOG_LOCATION}",
+      shellout_options
+    ).and_return(shell_out_result)
+    subject.service_main
   end
 
-  context 'when running chef-client' do
-    it "passes config params to new process with a default timeout of 2 hours (7200 seconds)" do
-      Chef::Config.merge!({:log_location => tempfile.path, :config_file => "test_config_file", :log_level => :info})
-      expect(instance).to receive(:configure_chef).twice
-      instance.service_init
-      allow(instance).to receive(:running?).and_return(true, false)
-      allow(instance.instance_variable_get(:@service_signal)).to receive(:wait)
-      allow(instance).to receive(:state).and_return(4)
-      expect(instance).to receive(:run_chef_client).and_call_original
-      expect(instance).to receive(:shell_out).with("chef-client  --no-fork -c test_config_file -L #{tempfile.path}", {:timeout => 7200}).and_return(shell_out_result)
-      instance.service_main
+  context 'has a log location configured' do
+    let(:tempfile) { Tempfile.new 'log_file' }
+    let(:config_options) do
+      {
+        log_location: tempfile.path,
+        config_file: "test_config_file",
+        log_level: :info
+      }
+    end
+
+    after do
       tempfile.unlink
     end
 
-    it "passes config params to new process with a the timeout specified in the config" do
-      Chef::Config.merge!({:log_location => tempfile.path, :config_file => "test_config_file", :log_level => :info})
+    it "uses the configured log location" do
+      expect(subject).to receive(:shell_out).with(
+        "chef-client  --no-fork -c test_config_file -L #{tempfile.path}",
+        shellout_options
+      ).and_return(shell_out_result)
+      subject.service_main
+    end
+
+    context 'configured to Event Logger' do
+      let(:config_options) do
+        {
+          log_location: Chef::Log::WinEvt.new,
+          config_file: "test_config_file",
+          log_level: :info
+        }
+      end
+
+      it "does not pass log location to new process" do
+        expect(subject).to receive(:shell_out).with(
+          "chef-client  --no-fork -c test_config_file",
+          shellout_options
+        ).and_return(shell_out_result)
+        subject.service_main
+      end
+    end
+  end
+
+  context 'configueres a watchdog timeout' do
+    let(:timeout) { 10 }
+
+    before do
       Chef::Config[:windows_service][:watchdog_timeout] = 10
-      expect(instance).to receive(:configure_chef).twice
-      instance.service_init
-      allow(instance).to receive(:running?).and_return(true, false)
-      allow(instance.instance_variable_get(:@service_signal)).to receive(:wait)
-      allow(instance).to receive(:state).and_return(4)
-      expect(instance).to receive(:run_chef_client).and_call_original
-      expect(instance).to receive(:shell_out).with("chef-client  --no-fork -c test_config_file -L #{tempfile.path}", {:timeout => 10}).and_return(shell_out_result)
-      instance.service_main
-      tempfile.unlink
+    end
+
+    it "passes watchdog timeout to new process" do
+      expect(subject).to receive(:shell_out).with(
+        "chef-client  --no-fork -c test_config_file -L #{Chef::Application::WindowsService::DEFAULT_LOG_LOCATION}",
+        shellout_options
+      ).and_return(shell_out_result)
+      subject.service_main
     end
   end
 end
diff --git a/spec/unit/workstation_config_loader_spec.rb b/spec/unit/workstation_config_loader_spec.rb
deleted file mode 100644
index 72631f3..0000000
--- a/spec/unit/workstation_config_loader_spec.rb
+++ /dev/null
@@ -1,283 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan at getchef.com>)
-# Copyright:: Copyright (c) 2014 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 'spec_helper'
-require 'tempfile'
-require 'chef/workstation_config_loader'
-
-describe Chef::WorkstationConfigLoader do
-
-  let(:explicit_config_location) { nil }
-
-  let(:env) { {} }
-
-  let(:config_loader) do
-    described_class.new(explicit_config_location).tap do |c|
-      allow(c).to receive(:env).and_return(env)
-    end
-  end
-
-  # Test methods that do I/O or reference external state which are stubbed out
-  # elsewhere.
-  describe "external dependencies" do
-    let(:config_loader) { described_class.new(nil) }
-
-    it "delegates to ENV for env" do
-      expect(config_loader.env).to equal(ENV)
-    end
-
-    it "tests a path's existence" do
-      expect(config_loader.path_exists?('/nope/nope/nope/nope/frab/jab/nab')).to be(false)
-      expect(config_loader.path_exists?(__FILE__)).to be(true)
-    end
-
-  end
-
-  describe "locating the config file" do
-    context "without an explicit config" do
-
-      before do
-        allow(config_loader).to receive(:path_exists?).with(an_instance_of(String)).and_return(false)
-      end
-
-      it "has no config if HOME is not set" do
-        expect(config_loader.config_location).to be(nil)
-        expect(config_loader.no_config_found?).to be(true)
-      end
-
-      context "when HOME is set and contains a knife.rb" do
-
-        let(:home) { "/Users/example.user" }
-
-        before do
-          allow(Chef::Util::PathHelper).to receive(:home).with('.chef').and_yield(File.join(home, '.chef'))
-          allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/knife.rb").and_return(true)
-        end
-
-        it "uses the config in HOME/.chef/knife.rb" do
-          expect(config_loader.config_location).to eq("#{home}/.chef/knife.rb")
-        end
-
-        context "and has a config.rb" do
-
-          before do
-            allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/config.rb").and_return(true)
-          end
-
-          it "uses the config in HOME/.chef/config.rb" do
-            expect(config_loader.config_location).to eq("#{home}/.chef/config.rb")
-          end
-
-          context "and/or a parent dir contains a .chef dir" do
-
-            let(:env_pwd) { "/path/to/cwd" }
-
-            before do
-              if Chef::Platform.windows?
-                env["CD"] = env_pwd
-              else
-                env["PWD"] = env_pwd
-              end
-
-              allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/knife.rb").and_return(true)
-              allow(File).to receive(:exist?).with("#{env_pwd}/.chef").and_return(true)
-              allow(File).to receive(:directory?).with("#{env_pwd}/.chef").and_return(true)
-            end
-
-            it "prefers the config from parent_dir/.chef" do
-              expect(config_loader.config_location).to eq("#{env_pwd}/.chef/knife.rb")
-            end
-
-            context "and the parent dir's .chef dir has a config.rb" do
-
-              before do
-                allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/config.rb").and_return(true)
-              end
-
-              it "prefers the config from parent_dir/.chef" do
-                expect(config_loader.config_location).to eq("#{env_pwd}/.chef/config.rb")
-              end
-
-              context "and/or the current working directory contains a .chef dir" do
-
-                let(:cwd) { Dir.pwd }
-
-                before do
-                  allow(config_loader).to receive(:path_exists?).with("#{cwd}/knife.rb").and_return(true)
-                end
-
-                it "prefers a knife.rb located in the cwd" do
-                  expect(config_loader.config_location).to eq("#{cwd}/knife.rb")
-                end
-
-                context "and the CWD's .chef dir has a config.rb" do
-
-                  before do
-                    allow(config_loader).to receive(:path_exists?).with("#{cwd}/config.rb").and_return(true)
-                  end
-
-                  it "prefers a config located in the cwd" do
-                    expect(config_loader.config_location).to eq("#{cwd}/config.rb")
-                  end
-
-
-                  context "and/or KNIFE_HOME is set" do
-
-                    let(:knife_home) { "/path/to/knife/home" }
-
-                    before do
-                      env["KNIFE_HOME"] = knife_home
-                      allow(config_loader).to receive(:path_exists?).with("#{knife_home}/knife.rb").and_return(true)
-                    end
-
-                    it "prefers a knife located in KNIFE_HOME" do
-                      expect(config_loader.config_location).to eq("/path/to/knife/home/knife.rb")
-                    end
-
-                    context "and KNIFE_HOME contains a config.rb" do
-
-                      before do
-                        env["KNIFE_HOME"] = knife_home
-                        allow(config_loader).to receive(:path_exists?).with("#{knife_home}/config.rb").and_return(true)
-                      end
-
-                      it "prefers a config.rb located in KNIFE_HOME" do
-                        expect(config_loader.config_location).to eq("/path/to/knife/home/config.rb")
-                      end
-
-                    end
-
-                  end
-                end
-              end
-            end
-          end
-        end
-      end
-
-      context "when the current working dir is inside a symlinked directory" do
-        before do
-          # pwd according to your shell is /home/someuser/prod/chef-repo, but
-          # chef-repo is a symlink to /home/someuser/codes/chef-repo
-          env["CD"] = "/home/someuser/prod/chef-repo" # windows
-          env["PWD"] = "/home/someuser/prod/chef-repo" # unix
-
-          allow(Dir).to receive(:pwd).and_return("/home/someuser/codes/chef-repo")
-        end
-
-        it "loads the config from the non-dereferenced directory path" do
-          expect(File).to receive(:exist?).with("/home/someuser/prod/chef-repo/.chef").and_return(false)
-          expect(File).to receive(:exist?).with("/home/someuser/prod/.chef").and_return(true)
-          expect(File).to receive(:directory?).with("/home/someuser/prod/.chef").and_return(true)
-
-          expect(config_loader).to receive(:path_exists?).with("/home/someuser/prod/.chef/knife.rb").and_return(true)
-
-          expect(config_loader.config_location).to eq("/home/someuser/prod/.chef/knife.rb")
-        end
-      end
-    end
-
-    context "when given an explicit config to load" do
-
-      let(:explicit_config_location) { "/path/to/explicit/config.rb" }
-
-      it "prefers the explicit config" do
-        expect(config_loader.config_location).to eq(explicit_config_location)
-      end
-
-    end
-  end
-
-
-  describe "loading the config file" do
-
-    context "when no explicit config is specifed and no implicit config is found" do
-
-      before do
-        allow(config_loader).to receive(:path_exists?).with(an_instance_of(String)).and_return(false)
-      end
-
-      it "skips loading" do
-        expect(config_loader.config_location).to be(nil)
-        expect(config_loader.load).to be(false)
-      end
-
-    end
-
-    context "when an explicit config is given but it doesn't exist" do
-
-      let(:explicit_config_location) { "/nope/nope/nope/frab/jab/nab" }
-
-      it "raises a configuration error" do
-        expect { config_loader.load }.to raise_error(Chef::Exceptions::ConfigurationError)
-      end
-
-    end
-
-    context "when the config file exists" do
-
-      let(:config_content) { "" }
-
-      let(:explicit_config_location) do
-        # could use described_class, but remove all ':' from the path if so.
-        t = Tempfile.new("Chef-WorkstationConfigLoader-rspec-test")
-        t.print(config_content)
-        t.close
-        t.path
-      end
-
-      after { File.unlink(explicit_config_location) if File.exists?(explicit_config_location) }
-
-      context "and is valid" do
-
-        let(:config_content) { "config_file_evaluated(true)" }
-
-        it "loads the config" do
-          expect(config_loader.load).to be(true)
-          expect(Chef::Config.config_file_evaluated).to be(true)
-        end
-
-        it "sets Chef::Config.config_file" do
-          config_loader.load
-          expect(Chef::Config.config_file).to eq(explicit_config_location)
-        end
-      end
-
-      context "and has a syntax error" do
-
-        let(:config_content) { "{{{{{:{{" }
-
-        it "raises a ConfigurationError" do
-          expect { config_loader.load }.to raise_error(Chef::Exceptions::ConfigurationError)
-        end
-      end
-
-      context "and raises a ruby exception during evaluation" do
-
-        let(:config_content) { ":foo\n:bar\nraise 'oops'\n:baz\n" }
-
-        it "raises a ConfigurationError" do
-          expect { config_loader.load }.to raise_error(Chef::Exceptions::ConfigurationError)
-        end
-      end
-
-    end
-
-  end
-
-end
diff --git a/tasks/external_tests.rb b/tasks/external_tests.rb
new file mode 100644
index 0000000..3050886
--- /dev/null
+++ b/tasks/external_tests.rb
@@ -0,0 +1,63 @@
+require 'tempfile'
+require 'bundler'
+
+CURRENT_GEM_NAME = 'chef'
+CURRENT_GEM_PATH = File.expand_path('../..', __FILE__)
+
+def bundle_exec_with_chef(test_gem, commands)
+  gem_path = Bundler.environment.specs[test_gem].first.full_gem_path
+  gemfile_path = File.join(gem_path, "Gemfile.#{CURRENT_GEM_NAME}-external-test")
+  gemfile = File.open(gemfile_path, "w")
+  begin
+    IO.read(File.join(gem_path, 'Gemfile')).each_line do |line|
+      if line =~ /^\s*gemspec/
+        next
+      elsif line =~ /^\s*gem '#{CURRENT_GEM_NAME}'|\s*gem "#{CURRENT_GEM_NAME}"/
+        next
+      elsif line =~ /^\s*dev_gem\s*['"](.+)['"]\s*$/
+        line = "gem '#{$1}', github: 'poise/#{$1}'"
+      elsif line =~ /\s*gem\s*['"]#{test_gem}['"]/ # foodcritic      end
+        next
+      end
+      gemfile.puts(line)
+    end
+    gemfile.puts("gem #{CURRENT_GEM_NAME.inspect}, path: #{CURRENT_GEM_PATH.inspect}")
+    gemfile.puts("gemspec path: #{gem_path.inspect}")
+    gemfile.close
+    Dir.chdir(gem_path) do
+      Bundler.with_clean_env do
+        unless system({ 'RUBYOPT' => nil, 'GEMFILE_MOD' => nil }, "bundle install --gemfile #{gemfile_path}")
+          raise "Error running bundle install --gemfile #{gemfile_path} in #{gem_path}: #{$?.exitstatus}\nGemfile:\n#{IO.read(gemfile_path)}"
+        end
+        Array(commands).each do |command|
+          unless system({ 'BUNDLE_GEMFILE' => gemfile_path, 'RUBYOPT' => nil, 'GEMFILE_MOD' => nil }, "bundle exec #{command}")
+            raise "Error running bundle exec #{command} in #{gem_path} with BUNDLE_GEMFILE=#{gemfile_path}: #{$?.exitstatus}\nGemfile:\n#{IO.read(gemfile_path)}"
+          end
+        end
+      end
+    end
+  ensure
+    File.delete(gemfile_path) if File.exist?(gemfile_path)
+  end
+end
+
+EXTERNAL_PROJECTS = {
+  "chef-zero"             => [ "rake spec", "rake cheffs" ],
+  "cheffish"              => "rake spec",
+  "chef-provisioning"     => "rake spec",
+  "chef-provisioning-aws" => "rake spec",
+  "chef-sugar"            => "rake",
+  "foodcritic"            => "rake test",
+  "chefspec"              => "rake",
+  "chef-rewind"           => "rake spec",
+  "poise"                 => "rake spec",
+  "halite"                => "rake spec"
+}
+
+task :external_specs => EXTERNAL_PROJECTS.keys.map { |g| :"#{g.sub("-","_")}_spec" }
+
+EXTERNAL_PROJECTS.each do |test_gem, commands|
+  task :"#{test_gem.gsub('-','_')}_spec" do
+    bundle_exec_with_chef(test_gem, commands)
+  end
+end
diff --git a/tasks/maintainers.rb b/tasks/maintainers.rb
new file mode 100644
index 0000000..73a422f
--- /dev/null
+++ b/tasks/maintainers.rb
@@ -0,0 +1,210 @@
+#
+# Copyright:: Copyright (c) 2015 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 'rake'
+
+SOURCE = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.toml")
+TARGET = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.md")
+
+# The list of repositories that teams should own
+REPOSITORIES = ["chef/chef", "chef/chef-census", "chef/chef-repo",
+                "chef/client-docs", "chef/ffi-yajl", "chef/libyajl2-gem",
+                "chef/mixlib-authentication", "chef/mixlib-cli",
+                "chef/mixlib-config", "chef/mixlib-install", "chef/mixlib-log",
+                "chef/mixlib-shellout", "chef/ohai", "chef/omnibus-chef"]
+
+begin
+  require 'tomlrb'
+  require 'octokit'
+  require 'pp'
+  task :default => :generate
+
+  namespace :maintainers do
+    desc "Generate MarkDown version of MAINTAINERS file"
+    task :generate do
+      out = "<!-- This is a generated file. Please do not edit directly -->\n\n"
+      out << "# " + source["Preamble"]["title"] + "\n\n"
+      out <<  source["Preamble"]["text"] + "\n"
+
+      # The project lead is a special case
+      out << "# " + source["Org"]["Lead"]["title"] + "\n\n"
+      out << format_person(source["Org"]["Lead"]["person"]) + "\n\n"
+
+      out << format_components(source["Org"]["Components"])
+      File.open(TARGET, "w") { |fn|
+        fn.write out
+      }
+    end
+
+    desc "Synchronize GitHub teams"
+    # there's a special @chef/client-maintainers team that's everyone
+    # and then there's a team per component
+    task :synchronize do
+      Octokit.auto_paginate = true
+      get_github_teams
+      prepare_teams(source["Org"]["Components"].dup)
+      sync_teams!
+    end
+  end
+
+  def github
+    @github ||= Octokit::Client.new(:netrc => true)
+  end
+
+  def source
+    @source ||= Tomlrb.load_file SOURCE
+  end
+
+  def teams
+    @teams ||= {"client-maintainers" => {"title" => "Client Maintainers"}}
+  end
+
+  def add_members(team, name)
+    teams["client-maintainers"]["members"] ||= []
+    teams["client-maintainers"]["members"] << name
+    teams[team] ||= {}
+    teams[team]["members"] ||= []
+    teams[team]["members"] << name
+  end
+
+  def set_team_title(team, title)
+    teams[team] ||= {}
+    teams[team]["title"] = title
+  end
+
+  def gh_teams
+    @gh_teams ||= {}
+  end
+
+  # we have to resolve team names to ids. While we're at it, we can get the privacy
+  # setting, so we know whether we need to update it
+  def get_github_teams
+    github.org_teams("chef").each do |team|
+      gh_teams[team[:slug]] = {"id" => team[:id], "privacy" => team[:privacy]}
+    end
+  end
+
+  def get_github_team(team)
+    github.team_members(gh_teams[team]["id"]).map do |member|
+      member[:login]
+    end.sort.uniq.map(&:downcase)
+  rescue
+    []
+  end
+
+  def create_team(team)
+    puts "creating new github team: #{team} with title: #{teams[team]["title"]} "
+    t = github.create_team("chef", name: team, description: teams[team]["title"],
+                       privacy: "closed", repo_names: REPOSITORIES,
+                       accept: "application/vnd.github.ironman-preview+json")
+    gh_teams[team] = { "id" => t[:id], "privacy" => t[:privacy] }
+  end
+
+  def compare_teams(current, desired)
+    # additions are the subtraction of the current state from the desired state
+    # deletions are the subtraction of the desired state from the current state
+    [desired - current, current - desired]
+  end
+
+  def prepare_teams(cmp)
+    %w(text paths).each { |k| cmp.delete(k) }
+    if cmp.key?("team")
+      team = cmp.delete("team")
+      add_members(team, cmp.delete("lieutenant")) if cmp.key?("lieutenant")
+      add_members(team, cmp.delete("maintainers")) if cmp.key?("maintainers")
+      set_team_title(team, cmp.delete("title"))
+    else
+      %w(maintainers lieutenant title).each { |k| cmp.delete(k) }
+    end
+    cmp.each { |_k, v| prepare_teams(v) }
+  end
+
+  def update_team(team, additions, deletions)
+    create_team(team) unless gh_teams.key?(team)
+    update_team_privacy(team)
+    add_team_members(team, additions)
+    remove_team_members(team, deletions)
+  rescue
+    puts "failed for #{team}"
+  end
+
+  def update_team_privacy(team)
+    return if gh_teams[team]["privacy"] == "closed"
+    puts "Setting #{team} privacy to closed from #{gh_teams[team]["privacy"]}"
+    github.update_team(gh_teams[team]["id"], privacy: "closed",
+                       accept: "application/vnd.github.ironman-preview+json")
+  end
+
+  def add_team_members(team, additions)
+    additions.each do |member|
+      puts "Adding #{member} to #{team}"
+      github.add_team_membership(gh_teams[team]["id"], member, role: "member",
+                                 accept: "application/vnd.github.ironman-preview+json")
+    end
+  end
+
+  def remove_team_members(team, deletions)
+    deletions.each do |member|
+      puts "Removing #{member} from #{team}"
+      github.remove_team_membership(gh_teams[team]["id"], member,
+                                    accept: "application/vnd.github.ironman-preview+json")
+    end
+  end
+
+  def sync_teams!
+    teams.each do |name, details|
+      current = get_github_team(name)
+      desired = details["members"].flatten.sort.uniq.map(&:downcase)
+      additions, deletions = compare_teams(current, desired)
+      update_team(name, additions, deletions)
+    end
+  end
+
+  def get_person(person)
+    source["people"][person]
+  end
+
+  def format_components(cmp)
+    out = "## " + cmp.delete("title") + "\n\n"
+    out << cmp.delete("text") + "\n" if cmp.has_key?("text")
+    out << "To mention the team, use @chef/#{cmp.delete("team")}\n\n" if cmp.has_key?("team")
+    if cmp.has_key?("lieutenant")
+      out << "### Lieutenant\n\n"
+      out << format_person(cmp.delete("lieutenant")) + "\n\n"
+    end
+    out << format_maintainers(cmp.delete("maintainers")) + "\n" if cmp.has_key?("maintainers")
+    cmp.delete("paths")
+    cmp.each {|k,v| out << format_components(v) }
+    out
+  end
+
+  def format_maintainers(people)
+    o = "### Maintainers\n\n"
+    people.each do |p|
+      o << format_person(p) + "\n"
+    end
+    o
+  end
+
+  def format_person(person)
+    mnt = get_person(person)
+    "* [#{mnt["Name"]}](https://github.com/#{mnt["GitHub"]})"
+  end
+
+rescue LoadError
+  STDERR.puts "\n*** TomlRb not available.\n\n"
+end
diff --git a/tasks/rspec.rb b/tasks/rspec.rb
index a6fc5a9..4d74fa0 100644
--- a/tasks/rspec.rb
+++ b/tasks/rspec.rb
@@ -25,13 +25,26 @@ CHEF_ROOT = File.join(File.dirname(__FILE__), "..")
 begin
   require 'rspec/core/rake_task'
 
+
+  desc "Run specs for Chef's Components"
+  task :component_specs do
+    Dir.chdir("chef-config") do
+      Bundler.with_clean_env do
+        sh("bundle install")
+        sh("bundle exec rake spec")
+      end
+    end
+  end
+
   task :default => :spec
 
+  task :spec => :component_specs
+
   desc "Run standard specs (minus long running specs)"
   RSpec::Core::RakeTask.new(:spec) do |t|
     # right now this just limits to functional + unit, but could also remove
     # individual tests marked long-running
-    t.pattern = FileList['spec/{functional,unit}/**/*_spec.rb']
+    t.pattern = FileList['spec/**/*_spec.rb']
   end
 
   namespace :spec do

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/chef.git



More information about the Pkg-ruby-extras-commits mailing list