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

Antonio Terceiro terceiro at moszumanska.debian.org
Tue Aug 25 20:01:03 UTC 2015


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

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

commit d99b66d416b5b404914b05283c65d69cefc067ac
Author: Antonio Terceiro <terceiro at debian.org>
Date:   Tue Aug 25 16:41:51 2015 -0300

    Imported Upstream version 12.4.1
---
 Rakefile                                           |  104 +-
 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/api_client.rb                             |   10 +-
 lib/chef/api_client_v1.rb                          |  325 +++++
 lib/chef/application.rb                            |    1 -
 lib/chef/application/client.rb                     |   27 +-
 lib/chef/audit/audit_reporter.rb                   |   19 +-
 .../zen_follower.rb => lib/chef/audit/logger.rb    |   24 +-
 lib/chef/audit/runner.rb                           |    6 +-
 lib/chef/chef_class.rb                             |   81 +-
 .../chef_fs/file_system/chef_server_root_dir.rb    |    4 +-
 lib/chef/client.rb                                 |  800 ++++++++---
 lib/chef/config.rb                                 |  725 +---------
 lib/chef/cookbook/metadata.rb                      |   14 +-
 lib/chef/cookbook_loader.rb                        |    2 +-
 lib/chef/cookbook_site_streaming_uploader.rb       |   20 +-
 lib/chef/dsl/definitions.rb                        |   44 +
 lib/chef/dsl/recipe.rb                             |   94 +-
 lib/chef/dsl/resources.rb                          |   31 +
 lib/chef/event_dispatch/base.rb                    |    9 +-
 lib/chef/event_dispatch/dispatcher.rb              |    2 +
 lib/chef/event_loggers/windows_eventlog.rb         |   12 +-
 lib/chef/exceptions.rb                             |   13 +-
 lib/chef/file_access_control/unix.rb               |    5 +
 .../file_content_management/deploy/mv_windows.rb   |   22 +-
 lib/chef/formatters/doc.rb                         |   22 +-
 .../error_inspectors/api_error_formatting.rb       |   20 +
 .../error_inspectors/compile_error_inspector.rb    |   30 +-
 .../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 +
 .../guard_interpreter/default_guard_interpreter.rb |    2 +
 .../resource_guard_interpreter.rb                  |    8 +-
 lib/chef/http/authenticator.rb                     |    8 +
 lib/chef/http/basic_client.rb                      |   16 +-
 lib/chef/http/json_input.rb                        |    7 +-
 lib/chef/key.rb                                    |  271 ++++
 lib/chef/knife.rb                                  |   24 +-
 lib/chef/knife/bootstrap.rb                        |    6 +
 lib/chef/knife/bootstrap/templates/chef-full.erb   |  193 ++-
 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/core/generic_presenter.rb           |    2 +-
 lib/chef/knife/core/subcommand_loader.rb           |    2 +-
 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 +
 .../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/ssh.rb                              |   54 +-
 lib/chef/knife/user_create.rb                      |  133 +-
 lib/chef/knife/user_delete.rb                      |   56 +-
 lib/chef/knife/user_edit.rb                        |   48 +-
 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/log.rb                                    |    2 +
 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/powershell_out.rb                   |   98 ++
 lib/chef/mixin/provides.rb                         |   28 +-
 .../pacman_package.rb => mixin/unformatter.rb}     |   20 +-
 lib/chef/{knife/client_edit.rb => mixin/uris.rb}   |   37 +-
 lib/chef/mixin/windows_architecture_helper.rb      |    7 +-
 lib/chef/mixin/windows_env_helper.rb               |   13 +-
 .../whyrun_safe_ruby_block.rb => mixin/wstring.rb} |   19 +-
 lib/chef/node.rb                                   |   23 +-
 lib/chef/node_map.rb                               |  242 ++--
 lib/chef/platform/handler_map.rb                   |   45 +
 .../chef/platform/priority_map.rb                  |   32 +-
 .../provider_handler_map.rb}                       |   19 +-
 lib/chef/platform/provider_mapping.rb              |  286 +---
 lib/chef/platform/provider_priority_map.rb         |   89 +-
 lib/chef/platform/query_helpers.rb                 |   11 +-
 .../resource_handler_map.rb}                       |   19 +-
 lib/chef/platform/resource_priority_map.rb         |   36 +-
 lib/chef/platform/service_helpers.rb               |   42 +-
 lib/chef/policy_builder/policyfile.rb              |    1 +
 lib/chef/provider.rb                               |   52 +
 lib/chef/provider/cron/unix.rb                     |    1 +
 lib/chef/provider/directory.rb                     |    3 +
 lib/chef/provider/dsc_resource.rb                  |   13 +-
 lib/chef/provider/dsc_script.rb                    |    2 +-
 lib/chef/provider/file.rb                          |    9 +-
 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                      |    1 +
 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                      |    2 +
 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                     |  138 +-
 lib/chef/provider/mount.rb                         |    1 -
 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                       |   35 +-
 lib/chef/provider/package/aix.rb                   |   16 +-
 lib/chef/provider/package/apt.rb                   |    7 +-
 lib/chef/provider/package/dpkg.rb                  |    8 +-
 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               |   10 +-
 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              |   17 +-
 lib/chef/provider/package/smartos.rb               |   11 +-
 lib/chef/provider/package/solaris.rb               |   16 +-
 lib/chef/provider/package/windows.rb               |   96 +-
 lib/chef/provider/package/windows/msi.rb           |    2 +-
 lib/chef/provider/package/yum.rb                   |  145 +-
 lib/chef/provider/package/zypper.rb                |   31 +-
 lib/chef/provider/powershell_script.rb             |  176 ++-
 lib/chef/provider/reboot.rb                        |    1 +
 lib/chef/provider/registry_key.rb                  |    2 +
 lib/chef/provider/remote_file.rb                   |    1 +
 lib/chef/provider/remote_file/content.rb           |    9 +-
 lib/chef/provider/remote_file/fetcher.rb           |   30 +-
 lib/chef/provider/remote_file/local_file.rb        |   14 +-
 .../remote_file/{local_file.rb => network_file.rb} |   20 +-
 lib/chef/provider/service.rb                       |   26 +
 lib/chef/provider/service/aix.rb                   |   25 +-
 lib/chef/provider/service/debian.rb                |    4 +-
 lib/chef/provider/service/freebsd.rb               |    2 +-
 lib/chef/provider/service/init.rb                  |    1 +
 lib/chef/provider/service/insserv.rb               |    2 +-
 lib/chef/provider/service/invokercd.rb             |    2 +-
 lib/chef/provider/service/macosx.rb                |    2 +-
 lib/chef/provider/service/openbsd.rb               |    2 +-
 lib/chef/provider/service/redhat.rb                |    4 +-
 lib/chef/provider/service/upstart.rb               |    5 +-
 lib/chef/provider/service/windows.rb               |    1 -
 lib/chef/provider/user.rb                          |    2 -
 lib/chef/provider/user/aix.rb                      |    5 +-
 lib/chef/provider/user/pw.rb                       |    1 +
 lib/chef/provider/user/solaris.rb                  |    2 +
 lib/chef/provider/user/useradd.rb                  |    1 +
 lib/chef/provider_resolver.rb                      |  154 +-
 lib/chef/providers.rb                              |    1 +
 lib/chef/resource.rb                               |  287 +++-
 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                      |    3 -
 lib/chef/resource/cookbook_file.rb                 |    4 +-
 lib/chef/resource/cron.rb                          |    6 +-
 lib/chef/resource/csh.rb                           |    1 -
 lib/chef/resource/deploy.rb                        |   14 +-
 lib/chef/resource/deploy_revision.rb               |   14 -
 lib/chef/resource/directory.rb                     |    6 +-
 lib/chef/resource/dpkg_package.rb                  |    5 -
 lib/chef/resource/dsc_resource.rb                  |    5 +-
 lib/chef/resource/dsc_script.rb                    |    7 +-
 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                       |    5 +-
 lib/chef/resource/file.rb                          |   24 +-
 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 +-
 lib/chef/resource/link.rb                          |    8 +-
 lib/chef/resource/log.rb                           |    7 +-
 lib/chef/resource/lwrp_base.rb                     |  177 +--
 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                       |    9 +-
 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                  |    7 +-
 lib/chef/resource/remote_directory.rb              |    8 +-
 lib/chef/resource/remote_file.rb                   |    9 +-
 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                        |    2 -
 lib/chef/resource/service.rb                       |    7 +-
 lib/chef/resource/smartos_package.rb               |    9 -
 lib/chef/resource/solaris_package.rb               |   15 +-
 lib/chef/resource/subversion.rb                    |    3 +-
 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               |   32 +-
 lib/chef/resource/windows_script.rb                |    5 +-
 lib/chef/resource/windows_service.rb               |    6 +-
 lib/chef/resource/yum_package.rb                   |    5 +-
 ...whyrun_safe_ruby_block.rb => zypper_package.rb} |   15 +-
 lib/chef/resource_builder.rb                       |    7 +
 lib/chef/resource_definition.rb                    |    1 +
 lib/chef/resource_reporter.rb                      |   15 +-
 lib/chef/resource_resolver.rb                      |  187 ++-
 lib/chef/resources.rb                              |    1 +
 lib/chef/rest.rb                                   |    1 +
 lib/chef/run_context.rb                            |    1 +
 lib/chef/run_list/versioned_recipe_list.rb         |   19 +
 lib/chef/run_status.rb                             |    6 +-
 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/path_helper.rb                       |  208 +--
 lib/chef/util/windows/net_user.rb                  |  191 +--
 lib/chef/version.rb                                |   12 +-
 lib/chef/win32/api.rb                              |    3 +-
 lib/chef/win32/api/installer.rb                    |    2 +-
 lib/chef/win32/api/net.rb                          |  117 +-
 lib/chef/win32/api/security.rb                     |   24 +
 lib/chef/win32/api/unicode.rb                      |    2 +-
 .../{resource/bff_package.rb => win32/eventlog.rb} |   28 +-
 lib/chef/win32/net.rb                              |  190 +++
 lib/chef/win32/security.rb                         |   53 +-
 lib/chef/win32/security/sid.rb                     |   17 +
 metadata.yml                                       |  167 ++-
 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 +
 spec/data/trusted_certs/opscode.pem                |  109 +-
 spec/functional/audit/runner_spec.rb               |   64 +-
 spec/functional/knife/ssh_spec.rb                  |    4 +-
 spec/functional/mixin/powershell_out_spec.rb       |   43 +
 .../provider/whyrun_safe_ruby_block_spec.rb        |    2 +-
 spec/functional/rebooter_spec.rb                   |    2 +-
 spec/functional/resource/aixinit_service_spec.rb   |    2 +-
 spec/functional/resource/execute_spec.rb           |   11 +-
 spec/functional/resource/file_spec.rb              |   25 +
 spec/functional/resource/group_spec.rb             |    5 +
 spec/functional/resource/link_spec.rb              |   16 +-
 spec/functional/resource/package_spec.rb           |    2 -
 spec/functional/resource/powershell_spec.rb        |   45 +-
 spec/functional/resource/user/useradd_spec.rb      |   28 +-
 spec/functional/resource/user/windows_spec.rb      |  125 ++
 spec/functional/shell_spec.rb                      |   35 +-
 spec/functional/win32/sid_spec.rb                  |   55 +
 spec/integration/client/client_spec.rb             |   82 +-
 spec/integration/knife/deps_spec.rb                |   22 +-
 spec/integration/knife/upload_spec.rb              |   18 +
 .../recipes/lwrp_inline_resources_spec.rb          |    2 +-
 ...{lwrp_inline_resources_spec.rb => lwrp_spec.rb} |   45 +-
 spec/integration/recipes/provider_choice.rb        |   36 +
 spec/integration/recipes/recipe_dsl_spec.rb        | 1507 ++++++++++++++++++++
 spec/spec_helper.rb                                |   41 +-
 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/shared/context/client.rb              |  277 ++++
 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/windows_script.rb   |    2 +-
 .../shared/integration/integration_helper.rb       |   11 +-
 spec/support/shared/shared_examples.rb             |    2 +-
 spec/support/shared/unit/api_versioning.rb         |   77 +
 .../support/shared/unit/knife_shared.rb            |   37 +-
 spec/support/shared/unit/provider/file.rb          |   39 +-
 spec/support/shared/unit/user_and_client_shared.rb |  115 ++
 spec/unit/api_client_spec.rb                       |   20 +-
 .../{api_client_spec.rb => api_client_v1_spec.rb}  |  251 +++-
 spec/unit/application/client_spec.rb               |    9 +-
 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_fs/file_pattern_spec.rb             |   18 +-
 spec/unit/client_spec.rb                           |  469 ++----
 spec/unit/config_spec.rb                           |  544 -------
 spec/unit/cookbook/cookbook_version_loader_spec.rb |    2 +-
 spec/unit/cookbook/metadata_spec.rb                |   15 +
 spec/unit/cookbook/syntax_check_spec.rb            |    2 +-
 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                      |   55 +
 spec/unit/dsl/resources_spec.rb                    |   85 ++
 spec/unit/environment_spec.rb                      |    2 +-
 spec/unit/event_dispatch/dispatcher_spec.rb        |   61 +
 spec/unit/exceptions_spec.rb                       |    6 +-
 .../deploy/mv_windows_spec.rb                      |   60 +
 spec/unit/formatters/doc_spec.rb                   |   46 +
 .../error_inspectors/api_error_formatting_spec.rb  |   77 +
 .../compile_error_inspector_spec.rb                |  261 ++--
 .../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/json_compat_spec.rb                      |    2 +-
 spec/unit/key_spec.rb                              |  634 ++++++++
 spec/unit/knife/bootstrap_spec.rb                  |   15 +-
 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/core/subcommand_loader_spec.rb     |   24 +-
 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 ++
 ...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/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                            |   40 +-
 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 +-
 spec/unit/mixin/path_sanity_spec.rb                |    4 +-
 spec/unit/mixin/powershell_out_spec.rb             |   70 +
 spec/unit/mixin/template_spec.rb                   |    4 +-
 spec/unit/mixin/unformatter_spec.rb                |   61 +
 spec/unit/mixin/uris_spec.rb                       |   57 +
 spec/unit/node_map_spec.rb                         |   17 +-
 spec/unit/node_spec.rb                             |    2 +-
 spec/unit/platform/query_helpers_spec.rb           |    2 +-
 spec/unit/platform_spec.rb                         |   62 +-
 spec/unit/policy_builder/policyfile_spec.rb        |   10 +-
 spec/unit/provider/deploy/revision_spec.rb         |    2 +-
 spec/unit/provider/deploy_spec.rb                  |    4 +-
 spec/unit/provider/directory_spec.rb               |  334 +++--
 spec/unit/provider/dsc_resource_spec.rb            |    6 +-
 spec/unit/provider/dsc_script_spec.rb              |    4 +-
 spec/unit/provider/execute_spec.rb                 |    2 +-
 spec/unit/provider/ifconfig/debian_spec.rb         |   10 -
 spec/unit/provider/package/aix_spec.rb             |   44 +-
 spec/unit/provider/package/dpkg_spec.rb            |    4 +-
 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             |  459 ++++--
 spec/unit/provider/package/rubygems_spec.rb        |   54 +-
 spec/unit/provider/package/smartos_spec.rb         |   90 +-
 spec/unit/provider/package/solaris_spec.rb         |   22 +-
 spec/unit/provider/package/windows_spec.rb         |  129 +-
 spec/unit/provider/package/yum_spec.rb             |  112 +-
 spec/unit/provider/package/zypper_spec.rb          |  215 +--
 spec/unit/provider/package_spec.rb                 |   40 +
 spec/unit/provider/powershell_spec.rb              |   64 +-
 spec/unit/provider/remote_directory_spec.rb        |    4 +-
 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/service/aix_service_spec.rb     |   37 +-
 spec/unit/provider/service/freebsd_service_spec.rb |   12 -
 spec/unit/provider/user/dscl_spec.rb               |    2 +-
 spec/unit/provider/user_spec.rb                    |    6 +-
 spec/unit/provider_resolver_spec.rb                |  848 ++++++-----
 spec/unit/provider_spec.rb                         |   20 +
 spec/unit/recipe_spec.rb                           |   69 +-
 spec/unit/resource/batch_spec.rb                   |    1 +
 spec/unit/resource/breakpoint_spec.rb              |    2 +-
 spec/unit/resource/cron_spec.rb                    |    2 +-
 spec/unit/resource/directory_spec.rb               |    2 +-
 spec/unit/resource/dsc_resource_spec.rb            |    2 +-
 spec/unit/resource/dsc_script_spec.rb              |    4 +-
 spec/unit/resource/env_spec.rb                     |    2 +-
 spec/unit/resource/erl_call_spec.rb                |    2 +-
 spec/unit/resource/file_spec.rb                    |    2 +-
 spec/unit/resource/group_spec.rb                   |    2 +-
 spec/unit/resource/ifconfig_spec.rb                |   16 +-
 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 +-
 spec/unit/resource/powershell_spec.rb              |    1 +
 spec/unit/resource/registry_key_spec.rb            |    2 +-
 spec/unit/resource/remote_file_spec.rb             |   15 +
 spec/unit/resource/ruby_block_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_collection_spec.rb              |    2 +-
 spec/unit/resource_resolver_spec.rb                |   53 +
 spec/unit/resource_spec.rb                         |  277 +++-
 spec/unit/rest_spec.rb                             |   30 +-
 spec/unit/role_spec.rb                             |    4 +-
 spec/unit/run_context_spec.rb                      |   72 +
 spec/unit/run_list/versioned_recipe_list_spec.rb   |  163 ++-
 spec/unit/run_list_spec.rb                         |    2 +-
 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 ----
 tasks/external_tests.rb                            |   29 +
 tasks/maintainers.rb                               |   69 +
 tasks/rspec.rb                                     |   15 +-
 506 files changed, 17400 insertions(+), 6574 deletions(-)

diff --git a/Rakefile b/Rakefile
index 70a45d9..628e0e1 100644
--- a/Rakefile
+++ b/Rakefile
@@ -17,31 +17,120 @@
 # 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 'rdoc/task'
-require './tasks/rspec.rb'
+require_relative 'tasks/rspec'
+require_relative 'tasks/external_tests'
+require_relative 'tasks/maintainers'
 
 GEM_NAME = "chef"
 
+desc "build Gems of Chef's components"
+task :package_components do
+  Dir.chdir("chef-config") do
+    sh "rake package"
+  end
+end
+
+task :package => :package_components
+
+desc "build and install chef's components"
+task :install_components => :package_components do
+  Dir.chdir("chef-config") do
+    sh "rake install"
+  end
+end
+
+task :install => :install_components
+
+desc "clean up builds of Chef's components"
+task :clobber_component_packages do
+  Dir.chdir("chef-config") do
+    sh "rake clobber_package"
+  end
+end
+
+task :clobber_package => :clobber_component_packages
+
+desc "Update the version number for Chef's components"
+task :update_components_versions do
+  Dir.chdir("chef-config") do
+    sh "rake version"
+  end
+end
+
+desc "Regenerate lib/chef/version.rb from VERSION file"
+task :version => :update_components_versions do
+  contents = <<-VERSION_RB
+# Copyright:: Copyright (c) 2010-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.
+
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# 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 = '#{VERSION}'
+end
+
+#
+# NOTE: the Chef::Version class is defined in version_class.rb
+#
+# 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.
+#
+VERSION_RB
+  version_rb_path = File.expand_path("../lib/chef/version.rb", __FILE__)
+  IO.write(version_rb_path, contents)
+end
+
 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}
+def with_clean_env(&block)
+  if defined?(Bundler)
+    Bundler.with_clean_env(&block)
+  else
+    block.call
+  end
+end
+
+desc "Build and install a chef gem"
+task :install => [:package] do
+  with_clean_env do
+    sh %{gem install pkg/#{GEM_NAME}-#{VERSION}.gem --no-rdoc --no-ri}
+  end
 end
 
 task :uninstall do
-  sh %{gem uninstall #{GEM_NAME} -x -v #{Chef::VERSION} }
+  sh %{gem uninstall #{GEM_NAME} -x -v #{VERSION} }
 end
 
 desc "Build it, tag it and ship it"
-task :ship => :gem do
-  sh("git tag #{Chef::VERSION}")
+task :ship => [:clobber_package, :gem] do
+  sh("git tag #{VERSION}")
   sh("git push opscode --tags")
   Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
     sh("gem push #{built_gem}")
@@ -79,3 +168,4 @@ begin
 rescue LoadError
   puts "yard is not available. (sudo) gem install yard to generate yard documentation."
 end
+
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/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_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..0563822 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -93,7 +93,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("*****************************************")
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index a5faee9..409680b 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -279,6 +279,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 +326,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,7 +448,7 @@ 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
+  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." +
@@ -456,18 +456,7 @@ class Chef::Application::Client < Chef::Application
   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/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..234d83a 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
@@ -115,8 +117,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..5cf4f95 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,74 @@ 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
 
+    #
     # 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 +114,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 +143,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 +162,28 @@ 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
   end
+
+  reset!
 end
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..824325f 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
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index d04a3db..3c86f52 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -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,279 @@ 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
+      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)
+
+        # 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
+        GC.start
+      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 +346,7 @@ class Chef
       end
     end
 
+    # @api private
     def formatters_for_run
       if Chef::Config.formatters.empty?
         [default_formatter]
@@ -195,6 +355,7 @@ class Chef
       end
     end
 
+    # @api private
     def default_formatter
       if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter]
         [:doc]
@@ -203,6 +364,7 @@ class Chef
       end
     end
 
+    # @api private
     def configure_event_loggers
       if Chef::Config.disable_event_logger
         []
@@ -219,8 +381,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 +393,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.strategy.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 +517,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 +565,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 +575,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 +662,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 +697,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,99 +777,121 @@ 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
-
-        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
+    # 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
 
-        run_status.run_id = request_id
-        run_status.start_clock
-        Chef::Log.info("Starting Chef Run for #{node.name}")
-        run_started
+      #
+      # 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
 
-        do_windows_admin_check
+      #
+      # 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
 
-        run_context = setup_run_context
+      #
+      # 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
 
-        if Chef::Config[:audit_mode] != :audit_only
-          converge_error = converge_and_save(run_context)
-        end
+      #
+      # 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
 
-        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
+      #
+      # 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
 
-        if converge_error || audit_error
-          e = Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
-          e.fill_backtrace
-          raise e
-        end
+      #
+      # Listeners to be run when the client run fails.
+      #
+      # @return [Array<Proc>]
+      #
+      # @api private
+      #
+      def run_failed_notifications
+        @run_failed_notifications ||= []
+      end
+    end
 
-        run_status.stop_clock
-        Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
-        run_completed_successfully
-        @events.run_completed(node)
+    #
+    # 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
 
-        # rebooting has to be the last thing we do, no exceptions.
-        Chef::Platform::Rebooter.reboot_if_needed!(node)
+    #
+    # 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
 
-        true
+    #
+    # Deprecated writers
+    #
 
-      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
-    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!"
 
     private
 
+    attr_reader :override_runlist
+    attr_reader :specific_recipes
+
     def empty_directory?(path)
       !File.exists?(path) || (Dir.entries(path).size <= 2)
     end
@@ -536,7 +923,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..9beb18b 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -20,727 +20,34 @@
 # 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
+# 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
 
-    # 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
+require 'chef-config/config'
 
-    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 }
-
-    # 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"
+require 'chef/platform/query_helpers'
 
-    # 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 }
-
-    # 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
-
-    # 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? and not Chef::Platform.windows_server_2003?
         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.
-    #
-    # 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
-
-    # 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.
-    #
-    # 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."
-      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/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index 781d3b4..01a98fd 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -286,9 +286,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
 
@@ -603,7 +607,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 +626,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_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..0302a51 100644
--- a/lib/chef/cookbook_site_streaming_uploader.rb
+++ b/lib/chef/cookbook_site_streaming_uploader.rb
@@ -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/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/recipe.rb b/lib/chef/dsl/recipe.rb
index c22f053..d69f0a8 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -21,6 +21,10 @@ 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/resource'
 
 class Chef
   module DSL
@@ -31,48 +35,10 @@ class Chef
     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
+      include Chef::Mixin::PowershellOut
 
-      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
+      include Chef::DSL::Resources
+      include Chef::DSL::Definitions
 
       #
       # Instantiates a resource (via #build_resource), then adds it to the
@@ -168,14 +134,52 @@ 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.")
+          Chef::Log.deprecation("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
     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/resources.rb b/lib/chef/dsl/resources.rb
new file mode 100644
index 0000000..f15beae
--- /dev/null
+++ b/lib/chef/dsl/resources.rb
@@ -0,0 +1,31 @@
+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
+      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) if method_defined?(dsl_name)
+      end
+    end
+  end
+end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 7274105..73fe25e 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -82,6 +82,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
@@ -239,13 +244,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
diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb
index 9f43f14..370f8c5 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
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..1b726d6 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -69,10 +69,17 @@ class Chef
     class ValidationFailed < ArgumentError; end
     class InvalidPrivateKey < ArgumentError; end
     class ConfigurationError < ArgumentError; end
+    class MissingKeyAttribute < ArgumentError; end
+    class KeyCommandInputError < ArgumentError; 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,6 +97,8 @@ class Chef
     class ConflictingMembersInGroup < ArgumentError; end
     class InvalidResourceReference < RuntimeError; end
     class ResourceNotFound < RuntimeError; end
+    class ProviderNotFound < RuntimeError; end
+    NoProviderAvailable = ProviderNotFound
     class VerificationNotFound < RuntimeError; end
 
     # Can't find a Resource of this type that is valid on this platform.
@@ -211,8 +220,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 +438,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
diff --git a/lib/chef/file_access_control/unix.rb b/lib/chef/file_access_control/unix.rb
index 472f30b..c53d832 100644
--- a/lib/chef/file_access_control/unix.rb
+++ b/lib/chef/file_access_control/unix.rb
@@ -197,6 +197,8 @@ class Chef
           # 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")
           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")
           # the user has specified a permission, but it matches the file, so behave idempotently
@@ -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/mv_windows.rb b/lib/chef/file_content_management/deploy/mv_windows.rb
index 7504123..0d16da9 100644
--- a/lib/chef/file_content_management/deploy/mv_windows.rb
+++ b/lib/chef/file_content_management/deploy/mv_windows.rb
@@ -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/formatters/doc.rb b/lib/chef/formatters/doc.rb
index 7144d00..e76a940 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
@@ -93,6 +93,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}"
@@ -175,17 +179,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
 
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..d64d5e7 100644
--- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
@@ -30,15 +30,16 @@ 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)
@@ -93,10 +94,21 @@ 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
 
       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/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..6372b20 100644
--- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -68,7 +68,8 @@ class Chef
         run_action = action || @resource.action
 
         begin
-          @resource.run_action(run_action)
+          # Coerce to an array to be safe.
+          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 +93,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/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/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/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..4a93697 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
@@ -358,7 +359,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,6 +401,8 @@ 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
 
@@ -483,6 +486,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 +551,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..5b29591 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -316,6 +316,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])
diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb
index a87ab8e..b0d7a6e 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,17 +212,17 @@ 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
 
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/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/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index 1f59515..a8705c7 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -23,7 +23,7 @@ class Chef
     class SubcommandLoader
 
       MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
-      MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}/}.freeze
+      MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
 
       attr_reader :chef_config_dir
       attr_reader :env
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/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/ssh.rb b/lib/chef/knife/ssh.rb
index 656baf5..68e01cf 100644
--- a/lib/chef/knife/ssh.rb
+++ b/lib/chef/knife/ssh.rb
@@ -160,6 +160,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 +193,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]
@@ -416,16 +425,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|
@@ -499,7 +498,6 @@ class Chef
 
         @longest = 0
 
-        configure_attribute
         configure_user
         configure_password
         configure_identity_file
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..b1cce5b 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,27 @@ 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 unchaged, 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/log.rb b/lib/chef/log.rb
index 682afce..9b27778 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
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..a3eacf7 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/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/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/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/knife/client_edit.rb b/lib/chef/mixin/uris.rb
similarity index 51%
copy from lib/chef/knife/client_edit.rb
copy to lib/chef/mixin/uris.rb
index c81bce9..0136b55 100644
--- a/lib/chef/knife/client_edit.rb
+++ b/lib/chef/mixin/uris.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# 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");
@@ -16,30 +16,29 @@
 # limitations under the License.
 #
 
-require 'chef/knife'
+require 'uri'
 
 class Chef
-  class Knife
-    class ClientEdit < Knife
-
-      deps do
-        require 'chef/api_client'
-        require 'chef/json_compat'
+  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
 
-      banner "knife client edit CLIENT (options)"
-
-      def run
-        @client_name = @name_args[0]
 
-        if @client_name.nil?
-          show_usage
-          ui.fatal("You must specify a client name")
-          exit 1
+      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
-
-        edit_object(Chef::ApiClient, @client_name)
       end
+
     end
   end
 end
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb
index a0ac34f..c5f3e1b 100644
--- a/lib/chef/mixin/windows_architecture_helper.rb
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -42,7 +42,7 @@ class Chef
           is_i386_process_on_x86_64_windows?
       end
 
-      def with_os_architecture(node)
+      def with_os_architecture(node, architecture: nil)
         node ||= begin
           os_arch = ENV['PROCESSOR_ARCHITEW6432'] ||
                     ENV['PROCESSOR_ARCHITECTURE']
@@ -51,9 +51,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
 
diff --git a/lib/chef/mixin/windows_env_helper.rb b/lib/chef/mixin/windows_env_helper.rb
index 490b235..a126801 100644
--- a/lib/chef/mixin/windows_env_helper.rb
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -21,11 +21,11 @@ require 'chef/exceptions'
 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
-
       if Chef::Platform.windows?
         include Chef::ReservedNames::Win32::API::System
       end
@@ -39,7 +39,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(
+            Chef::ReservedNames::Win32::Unicode.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/resource/whyrun_safe_ruby_block.rb b/lib/chef/mixin/wstring.rb
similarity index 70%
copy from lib/chef/resource/whyrun_safe_ruby_block.rb
copy to lib/chef/mixin/wstring.rb
index 6fa5383..bb6fdf4 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/mixin/wstring.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Phil Dibowitz (<phild at fb.com>)
-# Copyright:: Copyright (c) 2013 Facebook
+# 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");
@@ -17,14 +17,15 @@
 #
 
 class Chef
-  class Resource
-    class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
-
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :whyrun_safe_ruby_block
+  module Mixin
+    module WideString
+      def wstring(str)
+        if str.nil? || str.encoding == Encoding::UTF_16LE
+          str
+        else
+          str.to_wstring
+        end
       end
-
     end
   end
 end
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index 9823185..d507837 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -77,13 +77,30 @@ class Chef
       @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.
@@ -244,6 +261,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
 
@@ -354,7 +372,8 @@ class Chef
 
       self.tags # make sure they're defined
 
-      automatic_attrs[:recipes] = expansion.recipes
+      automatic_attrs[:recipes] = expansion.recipes.with_fully_qualified_names_and_version_constraints
+      automatic_attrs[:expanded_run_list] = expansion.recipes.with_fully_qualified_names_and_version_constraints
       automatic_attrs[:roles] = expansion.roles
 
       apply_expansion_attributes(expansion)
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index 2ca6d9b..d905c87 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/platform/handler_map.rb b/lib/chef/platform/handler_map.rb
new file mode 100644
index 0000000..001eb3d
--- /dev/null
+++ b/lib/chef/platform/handler_map.rb
@@ -0,0 +1,45 @@
+#
+# 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/node_map'
+
+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 }
+            if cmp < 0
+              Chef::Log.warn "You are overriding #{key} on #{new_matcher[:filters].inspect} with #{new_matcher[:value].inspect}: used to be #{matcher[:value].inspect}. Use override: true if this is what you intended."
+            elsif cmp > 0
+              Chef::Log.warn "You declared a new resource #{new_matcher[:value].inspect} for resource #{key}, but it comes alphabetically after #{matcher[:value].inspect} and has the same filters (#{new_matcher[:filters].inspect}), so it will not be used. Use override: true if you want to use it for #{key}."
+            end
+          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..38dd0e3 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,21 @@ 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)
+            class_name = resource_type.class.name ? resource_type.class.name.split('::').last :
+              convert_to_class_name(resource_type.resource_name.to_s)
+
             begin
-              Chef::Provider.const_get(resource_type.class.to_s.split('::').last)
+              result = Chef::Provider.const_get(class_name)
+              Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'resource_name #{convert_to_snake_case(class_name).to_sym.inspect}'.")
+              Chef::Log.warn("This will no longer work in Chef 13: you must use 'resource_name' to provide DSL.")
             rescue NameError
-              nil
             end
-          else
-            nil
           end
+          result
         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..b3948ea 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -21,11 +21,7 @@ class Chef
 
     class << self
       def windows?
-        if RUBY_PLATFORM =~ /mswin|mingw|windows/
-          true
-        else
-          false
-        end
+        ChefConfig.windows?
       end
 
       def windows_server_2003?
@@ -43,6 +39,11 @@ class Chef
         is_server_2003
       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
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..d50812e 100644
--- a/lib/chef/platform/service_helpers.rb
+++ b/lib/chef/platform/service_helpers.rb
@@ -42,34 +42,34 @@ class Chef
         # different services is NOT a design concern of this module.
         #
         def service_resource_providers
-          service_resource_providers = []
+          @service_resource_providers ||= [].tap do |service_resource_providers|
 
-          if ::File.exist?("/usr/sbin/update-rc.d")
-            service_resource_providers << :debian
-          end
+            if ::File.exist?("/usr/sbin/update-rc.d")
+              service_resource_providers << :debian
+            end
 
-          if ::File.exist?("/usr/sbin/invoke-rc.d")
-            service_resource_providers << :invokercd
-          end
+            if ::File.exist?("/usr/sbin/invoke-rc.d")
+              service_resource_providers << :invokercd
+            end
 
-          if ::File.exist?("/sbin/insserv")
-            service_resource_providers << :insserv
-          end
+            if ::File.exist?("/sbin/insserv")
+              service_resource_providers << :insserv
+            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
-          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
+            end
 
-          if ::File.exist?("/sbin/chkconfig")
-            service_resource_providers << :redhat
-          end
+            if ::File.exist?("/sbin/chkconfig")
+              service_resource_providers << :redhat
+            end
 
-          if systemd_sanity_check?
-            service_resource_providers << :systemd
-          end
+            if systemd_sanity_check?
+              service_resource_providers << :systemd
+            end
 
-          service_resource_providers
+          end
         end
 
         def config_for_service(service_name)
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index ac25b54..5991e3c 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -119,6 +119,7 @@ class Chef
 
         @node = Chef::Node.find_or_create(node_name)
         validate_policyfile
+        events.policyfile_loaded(policy)
         node
       rescue Exception => e
         events.node_load_failed(node_name, e, Chef::Config)
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 65a56cf..fec4f64 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -22,14 +22,19 @@ 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'
 
 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)
@@ -83,6 +88,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,6 +116,8 @@ 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?
         load_current_resource
@@ -165,6 +175,14 @@ class Chef
       converge_actions.add_action(descriptions, &block)
     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
+
     protected
 
     def converge_actions
@@ -191,5 +209,39 @@ class Chef
       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/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/directory.rb b/lib/chef/provider/directory.rb
index 416393a..4d5423d 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.
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 2812c15..379369b 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -53,7 +53,7 @@ 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!
@@ -63,7 +63,7 @@ class Chef
             meta_configuration['RefreshMode'] == 'Disabled'
           }
           err = ["The LCM must have its RefreshMode set to Disabled. "]
-          a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+          a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
           a.whyrun err + ["Assuming a previous resource sets the RefreshMode."]
           a.block_action!
         end
@@ -121,7 +121,14 @@ class Chef
         # 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"]
+
+        if result.return_value.is_a?(Array)
+          # WMF Feb 2015 Preview
+          result.return_value[0]["InDesiredState"]
+        else
+          # WMF April 2015 Preview
+          result.return_value["InDesiredState"]
+        end
       end
 
       def set_resource
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/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/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..f877ed2 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
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..468e1ec 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
 
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..b5efbb2 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -19,6 +19,7 @@
 #
 
 require 'chef/provider'
+require 'chef/dsl/include_recipe'
 
 class Chef
   class Provider
@@ -69,9 +70,6 @@ class Chef
 
       end
 
-      extend Chef::Mixin::ConvertToClassName
-      extend Chef::Mixin::FromFile
-
       include Chef::DSL::Recipe
 
       # These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
@@ -80,71 +78,95 @@ class Chef
       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})")
+
+          LWRPBase.loaded_lwrps[filename] = true
 
-      # DSL for defining a provider's actions.
-      def self.action(name, &block)
-        define_method("action_#{name}") do
-          instance_eval(&block)
+          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
+        # 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 use_inline_resources
+          extend InlineResources::ClassMethods
+          include InlineResources
+        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..2039e9a 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
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..513552e 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -43,6 +43,12 @@ class Chef
         true
       end
 
+      def check_resource_semantics!
+        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
 
@@ -464,10 +470,7 @@ 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
-            end
+        [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v }
       end
 
       # @todo: extract apt/dpkg specific preseeding to a helper class
@@ -487,6 +490,30 @@ class Chef
           false
         end
       end
+
+      private
+
+      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
+
     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..a262f1a 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -62,7 +62,7 @@ class Chef
               # 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|
+              shell_out_with_timeout("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])
@@ -79,7 +79,7 @@ class Chef
           # 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}")
+          status = shell_out_with_timeout("dpkg -s #{@current_resource.package_name}")
           status.stdout.each_line do |line|
             case line
             when DPKG_INSTALLED
@@ -134,13 +134,13 @@ class Chef
           run_noninteractive("dpkg-reconfigure #{name}")
         end
 
-        # Runs command via shell_out with magic environment to disable
+        # Runs command via shell_out_with_timeout 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.
         #
         # 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 })
+          shell_out_with_timeout!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
         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..83fc09c 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,7 +72,7 @@ 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}", :env => {"PKG_PATH" => pkg_path}).status
             Chef::Log.debug("#{new_resource.package_name} installed")
           end
         end
@@ -83,7 +83,7 @@ class Chef
           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}", :env => nil).status
         end
 
         private
@@ -94,7 +94,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 +103,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}\"", :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
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..c5d52a8 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 /^([\w\d+_.-]+)\s([\w\d~_.-]+)$/
                 @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 /^([\w\d+_.-]+)\s([\w\d~_.-]+)$/
               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..b5f7dbd 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'
@@ -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..e62f37d 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,17 @@ 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
 
         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..7ff0b71 100644
--- a/lib/chef/provider/package/windows.rb
+++ b/lib/chef/provider/package/windows.rb
@@ -16,14 +16,18 @@
 # 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"
@@ -36,19 +40,23 @@ class Chef
 
         # 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::Provider::Package::Windows::MSI.new(resource_for_provider)
             else
               raise "Unable to find a Chef::Provider::Package::Windows provider for installer_type '#{installer_type}'"
             end
@@ -71,6 +79,17 @@ class Chef
           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)
@@ -80,6 +99,71 @@ class Chef
         def remove_package(name, version)
           package_provider.remove_package(name, version)
         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
+
+        private
+
+        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))
+            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
+            Chef::Util::PathHelper.cleanpath(new_resource.source)
+          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/msi.rb b/lib/chef/provider/package/windows/msi.rb
index 9384529..31faa78 100644
--- a/lib/chef/provider/package/windows/msi.rb
+++ b/lib/chef/provider/package/windows/msi.rb
@@ -56,7 +56,7 @@ class Chef
             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)
             # We could use MsiConfigureProduct here, but we'll start off with msiexec
             Chef::Log.debug("#{@new_resource} removing MSI package '#{@new_resource.source}'")
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 49c6f6b..e8c0483 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1,4 +1,4 @@
-#
+
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
 # License:: Apache License, Version 2.0
@@ -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,7 +647,7 @@ 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
 
@@ -713,7 +714,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 +780,32 @@ class Chef
             @next_refresh = :none
           end
 
+          def python_bin
+            yum_executable = which("yum")
+            if yum_executable && shabang?(yum_executable)
+              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 occured 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].chomp
+          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
@@ -958,6 +985,17 @@ class Chef
         # 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 +1004,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 +1021,13 @@ 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]})
+          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 +1044,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 +1104,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 +1127,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 +1178,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,16 +1220,16 @@ 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(' ')}")
@@ -1219,11 +1280,15 @@ 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}")
 
@@ -1240,22 +1305,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 +1369,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..ed44dee 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -24,71 +24,153 @@ 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
+        valid_syntax = validate_script_syntax!
+        super if valid_syntax
+      end
+
+      def flags
+        # 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'.
+        interpreter_flags = [default_interpreter_flags, '-File'].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 |
+          user_script_file.puts("{#{@new_resource.code}}")
+          user_script_file.close
 
-      def initialize (new_resource, run_context)
-        super(new_resource, run_context, '.ps1')
-        normalize_script_exit_status(new_resource.code)
+          validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command #{user_script_file.path}"
+
+          # For consistency with other script resources, allow even syntax errors
+          # to be suppressed if the returns attribute would have suppressed it
+          # at converge.
+          valid_returns = [0]
+          specified_returns = @new_resource.returns.is_a?(Integer) ?
+            [@new_resource.returns] :
+            @new_resource.returns
+          valid_returns.concat([1]) if specified_returns.include?(1)
+
+          result = shell_out!(validation_command, {returns: valid_returns})
+          result.exitstatus == 0
+        end
       end
 
-      def flags
-        default_flags = [
+      def default_interpreter_flags
+        # '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
+}
+
+# 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..cd62f7c 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?
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/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/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/service.rb b/lib/chef/provider/service.rb
index 75da2dd..15dd72c 100644
--- a/lib/chef/provider/service.rb
+++ b/lib/chef/provider/service.rb
@@ -168,6 +168,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..09ed4bb 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..46c23fd 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -22,11 +22,11 @@ class Chef
   class Provider
     class Service
       class Debian < Chef::Provider::Service::Init
+        provides :service, platform_family: 'debian'
+
         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
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 9204e3e..6c78f86 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -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/init.rb b/lib/chef/provider/service/init.rb
index 0a219a6..355e98a 100644
--- a/lib/chef/provider/service/init.rb
+++ b/lib/chef/provider/service/init.rb
@@ -18,6 +18,7 @@
 
 require 'chef/provider/service/simple'
 require 'chef/mixin/command'
+require 'chef/platform/service_helpers'
 
 class Chef
   class Provider
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 31965a4..2fd2eac 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -24,7 +24,7 @@ class Chef
     class Service
       class Insserv < Chef::Provider::Service::Init
 
-        provides :service, os: "linux"
+        provides :service, platform_family: %w(debian rhel fedora suse)
 
         def self.provides?(node, resource)
           super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb
index 5ff24e0..3902254 100644
--- a/lib/chef/provider/service/invokercd.rb
+++ b/lib/chef/provider/service/invokercd.rb
@@ -23,7 +23,7 @@ class Chef
     class Service
       class Invokercd < Chef::Provider::Service::Init
 
-        provides :service, platform_family: "debian"
+        provides :service, platform_family: 'debian', override: true
 
         def self.provides?(node, resource)
           super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 7cfe57a..7324822 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
diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb
index d509ee1..173191e 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
 
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 8509531..2330d88 100644
--- a/lib/chef/provider/service/redhat.rb
+++ b/lib/chef/provider/service/redhat.rb
@@ -23,11 +23,11 @@ class Chef
     class Service
       class Redhat < Chef::Provider::Service::Init
 
+        provides :service, platform_family: %w(rhel fedora suse)
+
         CHKCONFIG_ON = /\d:on/
         CHKCONFIG_MISSING = /No such/
 
-        provides :service, platform_family: [ "rhel", "fedora", "suse" ]
-
         def self.provides?(node, resource)
           super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
         end
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 8d4aa41..8809d1c 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -25,9 +25,10 @@ class Chef
   class Provider
     class Service
       class Upstart < Chef::Provider::Service::Simple
-        UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
 
-        provides :service, os: "linux"
+        provides :service, platform_family: 'debian', override: true
+
+        UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
 
         def self.provides?(node, resource)
           super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
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/user.rb b/lib/chef/provider/user.rb
index f6ac724..244b11d 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
@@ -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/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..b242095 100644
--- a/lib/chef/provider/user/solaris.rb
+++ b/lib/chef/provider/user/solaris.rb
@@ -22,6 +22,8 @@ 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
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_resolver.rb b/lib/chef/provider_resolver.rb
index 5e88722..8459bc1 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/resource.rb b/lib/chef/resource.rb
index d934ec8..74d52a7 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -22,6 +22,7 @@ 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'
@@ -31,9 +32,14 @@ require 'chef/node_map'
 require 'chef/node'
 require 'chef/platform'
 require 'chef/resource/resource_notification'
+require 'chef/provider_resolver'
+require 'chef/resource_resolver'
+require 'set'
 
 require 'chef/mixin/deprecation'
 require 'chef/mixin/provides'
+require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
 
 class Chef
   class Resource
@@ -48,6 +54,12 @@ 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
+
+    NULL_ARG = Object.new
+
     #
     # The node the current Chef run is using.
     #
@@ -79,7 +91,6 @@ class Chef
       run_context.resource_collection.find(*args)
     end
 
-
     #
     # Resource User Interface (for users)
     #
@@ -98,8 +109,8 @@ class Chef
       @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 = {}
@@ -162,26 +173,28 @@ 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
+        # Pull the action from the class if it's not set
+        @action || self.class.default_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.
     #
@@ -467,7 +480,7 @@ class Chef
     #
     # @return [Hash{Symbol => Object}] A Hash of attribute => value for the
     #   Resource class's `state_attrs`.
-    def state
+    def state_for_resource_reporter
       self.class.state_attrs.inject({}) do |state_attrs, attr_name|
         state_attrs[attr_name] = send(attr_name)
         state_attrs
@@ -475,6 +488,15 @@ class Chef
     end
 
     #
+    # 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.
+    #
+    # @deprecated
+    #
+    alias_method :state, :state_for_resource_reporter
+
+    #
     # The value of the identity attribute, if declared. Falls back to #name if
     # no identity attribute is declared.
     #
@@ -588,14 +610,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
@@ -749,6 +771,12 @@ class Chef
     #   have.
     #
     attr_accessor :allowed_actions
+    def allowed_actions(value=NULL_ARG)
+      if value != NULL_ARG
+        self.allowed_actions = value
+      end
+      @allowed_actions
+    end
 
     #
     # Whether or not this resource was updated during an action.  If multiple
@@ -807,19 +835,15 @@ class Chef
     end
 
     #
-    # The DSL name of this resource (e.g. `package` or `yum_package`)
-    #
-    # @return [String] The DSL name of this resource.
-    def self.dsl_name
-      convert_to_snake_case(name, 'Chef::Resource')
-    end
-
+    # The display name of this resource type, for printing purposes.
     #
-    # 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 +876,61 @@ 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=NULL_ARG)
+      # Setter
+      if name != NULL_ARG
+        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
+
+    #
     # 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,9 +944,64 @@ 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=NULL_ARG)
+      unless action_name.equal?(NULL_ARG)
+        @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
 
 
@@ -945,10 +1079,36 @@ class Chef
       # NOTE: that we do not support unregistering classes as descendents 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 +1120,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)
@@ -1016,7 +1202,6 @@ 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.action = action
       provider
@@ -1090,30 +1275,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 +1323,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/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..0c2fdfa 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
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..3e5255b 100644
--- a/lib/chef/resource/deploy.rb
+++ b/lib/chef/resource/deploy.rb
@@ -51,15 +51,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,7 +69,6 @@ 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"
@@ -77,7 +76,6 @@ class Chef
         @shallow_clone = false
         @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
@@ -281,6 +279,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..38adf24 100644
--- a/lib/chef/resource/dpkg_package.rb
+++ b/lib/chef/resource/dpkg_package.rb
@@ -25,11 +25,6 @@ class Chef
 
       provides :dpkg_package, os: "linux"
 
-      def initialize(name, run_context=nil)
-        super
-        @resource_name = :dpkg_package
-      end
-
     end
   end
 end
diff --git a/lib/chef/resource/dsc_resource.rb b/lib/chef/resource/dsc_resource.rb
index 912b683..5db00f4 100644
--- a/lib/chef/resource/dsc_resource.rb
+++ b/lib/chef/resource/dsc_resource.rb
@@ -25,13 +25,12 @@ class Chef
 
       include Chef::DSL::Powershell
 
+      default_action :run
+
       def initialize(name, run_context)
         super
         @properties = {}
-        @resource_name = :dsc_resource
         @resource = nil
-        @allowed_actions.push(:run)
-        @action = :run
       end
 
       def resource(value=nil)
diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb
index cf96ef6..2877f61 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -22,13 +22,12 @@ class Chef
   class Resource
     class DscScript < Chef::Resource
 
-      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..ec669a7 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,7 +46,6 @@ class Chef
         @returns = 0
         @timeout = nil
         @user = nil
-        @allowed_actions.push(:run)
         @umask = nil
         @default_guard_interpreter = :execute
         @is_guard_interpreter = false
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/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/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..ef3c2b5 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -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,100 @@ 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("LWRP 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
+          # Make a useful string for the class (rather than <Class:312894723894>)
+          resource_class.instance_eval do
+            define_singleton_method(:to_s) do
+              "LWRP resource #{resource_name} from cookbook #{cookbook_name}"
+            end
+            define_singleton_method(:inspect) { to_s }
+          end
 
-        resource_class
-      end
+          Chef::Log.debug("Loaded contents of #{filename} into resource #{resource_name} (#{resource_class})")
 
-      # 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
-
-      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
+        # Define an attribute on this resource, including optional validation
+        # parameters.
+        def attribute(attr_name, validation_opts={})
+          define_method(attr_name) do |arg=nil|
+            set_or_return(attr_name.to_sym, arg, validation_opts)
           end
         end
 
-        @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 node
+          run_context ? run_context.node : nil
+        end
 
-      def self.lazy(&block)
-        DelayedEvaluator.new(&block)
-      end
+        def lazy(&block)
+          DelayedEvaluator.new(&block)
+        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..5be1c34 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -22,24 +22,23 @@ 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
+        @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..4ed0d4a 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)
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/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..30bed36 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
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index 36df7c8..aa59b54 100644
--- a/lib/chef/resource/service.rb
+++ b/lib/chef/resource/service.rb
@@ -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,7 @@ 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)
       end
 
       def service_name(arg=nil)
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..ae6a37c 100644
--- a/lib/chef/resource/subversion.rb
+++ b/lib/chef/resource/subversion.rb
@@ -22,13 +22,12 @@ 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
       end
 
       # Override exception to strip password if any, so it won't appear in logs and different Chef notifications
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..a76765c 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
@@ -69,10 +71,30 @@ class Chef
           @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..48e2b53 100644
--- a/lib/chef/resource/windows_script.rb
+++ b/lib/chef/resource/windows_script.rb
@@ -22,6 +22,7 @@ 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 +31,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
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..4d54f60 100644
--- a/lib/chef/resource/yum_package.rb
+++ b/lib/chef/resource/yum_package.rb
@@ -22,13 +22,10 @@ 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
       end
@@ -38,7 +35,7 @@ class Chef
         set_or_return(
           :arch,
           arg,
-          :kind_of => [ String ]
+          :kind_of => [ String, Array ]
         )
       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..9e9f704 100644
--- a/lib/chef/resource_builder.rb
+++ b/lib/chef/resource_builder.rb
@@ -18,6 +18,10 @@
 
 # NOTE: this was extracted from the Recipe DSL mixin, relevant specs are in spec/unit/recipe_spec.rb
 
+require 'chef/exceptions'
+require 'chef/resource'
+require 'chef/log'
+
 class Chef
   class ResourceBuilder
     attr_reader :type
@@ -46,6 +50,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
 
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..7d13a5a 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
@@ -220,7 +213,7 @@ 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
 
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
index ff9d7ae..47b3df1 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..af4dd8a 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -80,6 +80,7 @@ require 'chef/resource/windows_package'
 require 'chef/resource/yum_package'
 require 'chef/resource/lwrp_base'
 require 'chef/resource/bff_package'
+require 'chef/resource/zypper_package'
 
 begin
   # Optional resources chef_node, chef_client, machine, machine_image, etc.
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index 2612714..f87cec9 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)
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 4f0215b..44b05f0 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -86,6 +86,7 @@ class Chef
       @reboot_info = {}
 
       @node.run_context = self
+      @node.set_cookbook_attribute
       @cookbook_compiler = nil
     end
 
diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb
index 0eefded..2824f08 100644
--- a/lib/chef/run_list/versioned_recipe_list.rb
+++ b/lib/chef/run_list/versioned_recipe_list.rb
@@ -63,6 +63,25 @@ 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
     end
   end
 end
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/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/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/windows/net_user.rb b/lib/chef/util/windows/net_user.rb
index 5df1a8a..26fbe53 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
 
@@ -120,49 +91,32 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
     @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 +136,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 +155,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 +164,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/version.rb b/lib/chef/version.rb
index c2f287c..bb44f39 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.4.1'
 end
 
 #
diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb
index efa632f..e9d2738 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
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..72caf46 100644
--- a/lib/chef/win32/api/net.rb
+++ b/lib/chef/win32/api/net.rb
@@ -32,8 +32,24 @@ 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
+
+        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
+        ERROR_ACCESS_DENIED = 5
 
         ffi_lib "netapi32"
 
@@ -67,6 +83,57 @@ class Chef
             :usri3_profile, :LPWSTR,
             :usri3_home_dir_drive, :LPWSTR,
             :usri3_password_expired, :DWORD
+
+          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 usri3_logon_hours
+            val = self[:usri3_logon_hours]
+            if !val.nil? && !val.null?
+              val.read_bytes(21)
+            else
+              nil
+            end
+          end
+
+          def as_ruby
+            members.inject({}) do |memo, key|
+              memo[key] = get(key)
+              memo
+            end
+          end
+        end
+
+        class LOCALGROUP_MEMBERS_INFO_3 < FFI::Struct
+          layout :lgrmi3_domainandname, :LPWSTR
         end
 
 # NET_API_STATUS NetUserEnum(
@@ -85,6 +152,52 @@ class Chef
 #   _In_  LPVOID Buffer
 # );
         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 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
+
       end
     end
   end
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/unicode.rb b/lib/chef/win32/api/unicode.rb
index 0b2cb09..2e3a599 100644
--- a/lib/chef/win32/api/unicode.rb
+++ b/lib/chef/win32/api/unicode.rb
@@ -139,7 +139,7 @@ int WideCharToMultiByte(
           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"
+          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
diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/win32/eventlog.rb
similarity index 51%
copy from lib/chef/resource/bff_package.rb
copy to lib/chef/win32/eventlog.rb
index 917f0d1..24af2da 100644
--- a/lib/chef/resource/bff_package.rb
+++ b/lib/chef/win32/eventlog.rb
@@ -1,7 +1,7 @@
 #
-# Author:: Deepali Jagtap (<deepali.jagtap at clogeny.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
-# License:: Apache License, Version 2.0
+# 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.
@@ -16,20 +16,16 @@
 # limitations under the License.
 #
 
-require 'chef/resource/package'
-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
+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
+
+    require 'win32/eventlog'
+    Chef::Win32EventLogLoaded = true
   end
 end
-
-
diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb
new file mode 100644
index 0000000..1349091
--- /dev/null
+++ b/lib/chef/win32/net.rb
@@ -0,0 +1,190 @@
+#
+# 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/wstring'
+
+class Chef
+  module ReservedNames::Win32
+    class NetUser
+      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 ERROR_ACCESS_DENIED
+          "The user does not have access to the requested information."
+        else
+          "Received unknown error code (#{code})"
+        end
+
+        formatted_message = ""
+        formatted_message << "---- Begin Win32 API output ----\n"
+        formatted_message << "Net Api Error Code: #{code}\n"
+        formatted_message << "Net Api Error Message: #{msg}\n"
+        formatted_message << "---- End Win32 API output ----\n"
+
+        raise Chef::Exceptions::Win32APIError, msg + "\n" + formatted_message
+      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
+
+    end
+  end
+end
diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb
index 3902d8c..5c83180 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/wstring'
 
 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/metadata.yml b/metadata.yml
index 4bb3d01..6a858e7 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.4.1
 platform: ruby
 authors:
 - Adam Jacob
 autorequire: 
 bindir: bin
 cert_chain: []
-date: 2015-04-28 00:00:00.000000000 Z
+date: 2015-07-08 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.4.1
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
-    - - "~>"
+    - - '='
       - !ruby/object:Gem::Version
-        version: '2.0'
+        version: 12.4.1
 - !ruby/object:Gem::Dependency
   name: mixlib-cli
   requirement: !ruby/object:Gem::Requirement
@@ -104,22 +104,16 @@ dependencies:
   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
@@ -329,6 +329,20 @@ 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: rack
   requirement: !ruby/object:Gem::Requirement
     requirements:
@@ -343,22 +357,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
@@ -543,9 +571,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 +587,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
@@ -659,6 +690,7 @@ files:
 - lib/chef/dsl.rb
 - lib/chef/dsl/audit.rb
 - lib/chef/dsl/data_query.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 +698,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
@@ -732,6 +765,7 @@ 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
@@ -744,6 +778,11 @@ files:
 - 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
@@ -800,6 +839,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,6 +859,12 @@ 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/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/role_bulk_delete.rb
@@ -845,13 +898,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,16 +932,20 @@ 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/provides.rb
 - lib/chef/mixin/recipe_definition_dsl_core.rb
 - lib/chef/mixin/securable.rb
 - lib/chef/mixin/shell_out.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/windows_architecture_helper.rb
 - lib/chef/mixin/windows_env_helper.rb
+- lib/chef/mixin/wstring.rb
 - lib/chef/mixin/xml_escape.rb
 - lib/chef/mixins.rb
 - lib/chef/monkey_patches/net-ssh-multi.rb
@@ -895,10 +960,14 @@ 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
@@ -987,6 +1056,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
@@ -1098,6 +1168,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 +1203,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
@@ -1173,11 +1245,13 @@ files:
 - 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/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
@@ -1486,6 +1560,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
@@ -1522,6 +1597,7 @@ 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_service_spec.rb
 - spec/functional/rest_spec.rb
 - spec/functional/run_lock_spec.rb
@@ -1534,6 +1610,7 @@ files:
 - spec/functional/win32/registry_helper_spec.rb
 - spec/functional/win32/security_spec.rb
 - spec/functional/win32/service_manager_spec.rb
+- spec/functional/win32/sid_spec.rb
 - spec/functional/win32/versions_spec.rb
 - spec/integration/client/client_spec.rb
 - spec/integration/client/ipv6_spec.rb
@@ -1554,6 +1631,9 @@ 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/solo/solo_spec.rb
 - spec/rcov.opts
 - spec/scripts/ssl-serve.rb
@@ -1562,10 +1642,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
@@ -1581,7 +1664,9 @@ files:
 - 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/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 +1683,21 @@ 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/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 +1708,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
@@ -1632,7 +1722,6 @@ files:
 - spec/unit/chef_spec.rb
 - spec/unit/client_spec.rb
 - spec/unit/config_fetcher_spec.rb
-- spec/unit/config_spec.rb
 - spec/unit/cookbook/chefignore_spec.rb
 - spec/unit/cookbook/cookbook_version_loader_spec.rb
 - spec/unit/cookbook/file_vendor_spec.rb
@@ -1657,9 +1746,11 @@ 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/exceptions_spec.rb
 - spec/unit/file_access_control_spec.rb
 - spec/unit/file_cache_spec.rb
@@ -1667,6 +1758,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 +1771,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 +1781,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
@@ -1731,6 +1826,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 +1843,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 +1881,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 +1896,13 @@ 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/securable_spec.rb
 - spec/unit/mixin/shell_out_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
@@ -1877,6 +1990,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
@@ -1984,6 +2098,7 @@ 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
@@ -2005,6 +2120,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 +2129,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
@@ -2024,8 +2139,10 @@ files:
 - spec/unit/version_constraint_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: {}
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/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/audit/runner_spec.rb b/spec/functional/audit/runner_spec.rb
index 4949428..aae8fcf 100644
--- a/spec/functional/audit/runner_spec.rb
+++ b/spec/functional/audit/runner_spec.rb
@@ -46,22 +46,12 @@ 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
-
     shared_context "passing audit" do
       let(:audits) do
         should_pass = lambda do
@@ -84,50 +74,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/knife/ssh_spec.rb b/spec/functional/knife/ssh_spec.rb
index 5b8ad6f..6608d05 100644
--- a/spec/functional/knife/ssh_spec.rb
+++ b/spec/functional/knife/ssh_spec.rb
@@ -165,7 +165,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 +177,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/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..485e98f 100644
--- a/spec/functional/rebooter_spec.rb
+++ b/spec/functional/rebooter_spec.rb
@@ -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/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/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..529af52 100644
--- a/spec/functional/resource/group_spec.rb
+++ b/spec/functional/resource/group_spec.rb
@@ -372,6 +372,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/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/package_spec.rb b/spec/functional/resource/package_spec.rb
index 5c17ca0..8d37b07 100644
--- a/spec/functional/resource/package_spec.rb
+++ b/spec/functional/resource/package_spec.rb
@@ -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_spec.rb
index 56a905e..17ae8cb 100644
--- a/spec/functional/resource/powershell_spec.rb
+++ b/spec/functional/resource/powershell_spec.rb
@@ -56,14 +56,13 @@ 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
       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,6 +70,30 @@ 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
+      # 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
       resource.code(arbitrary_nonzero_process_exit_code_content)
@@ -99,7 +122,19 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
     it "returns 1 if the last command was a cmdlet that failed and was preceded by a successfully executed non-cmdlet Windows binary" do
       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 an error if the script is not syntactically correct and returns is not set to 1" do
+      resource.code('if({)')
+      resource.returns(0)
+      expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
+    end
+
+    it "returns 1 if the script provided to the code attribute is not syntactically correct" do
+      resource.code('if({)')
+      resource.returns(1)
+      expect { resource.run_action(:run) }.not_to raise_error
     end
 
     # This somewhat ambiguous case, two failures of different types,
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..5e68478
--- /dev/null
+++ b/spec/functional/resource/user/windows_spec.rb
@@ -0,0 +1,125 @@
+# 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
+  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/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/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/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb
index b5c5e12..8c72048 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
@@ -319,7 +342,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 +364,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/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/upload_spec.rb b/spec/integration/knife/upload_spec.rb
index cef4f54..826ecec 100644
--- a/spec/integration/knife/upload_spec.rb
+++ b/spec/integration/knife/upload_spec.rb
@@ -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 }
diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb
index b4c4e6c..e70605d 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:
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..a4de6ae
--- /dev/null
+++ b/spec/integration/recipes/recipe_dsl_spec.rb
@@ -0,0 +1,1507 @@
+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 'resource_name :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 Thingy9 provides :thingy9" do
+        before(:context) {
+          class RecipeDSLSpecNamespace::Thingy9 < BaseThingy
+            resource_name :thingy9
+          end
+        }
+
+        it "declaring a resource providing the same :thingy9 produces a warning" do
+          expect(Chef::Log).to receive(:warn).with("You declared a new resource RecipeDSLSpecNamespace::Thingy9AlternateProvider for resource thingy9, but it comes alphabetically after RecipeDSLSpecNamespace::Thingy9 and has the same filters ({}), so it will not be used. Use override: true if you want to use it for thingy9.")
+          class RecipeDSLSpecNamespace::Thingy9AlternateProvider < BaseThingy
+            resource_name :thingy9
+          end
+        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/spec_helper.rb b/spec/spec_helper.rb
index fb284c7..8e24e03 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -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'
@@ -84,12 +87,14 @@ 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_PLATFORM = TEST_NODE['platform']
+TEST_PLATFORM_VERSION = TEST_NODE['platform_version']
 
 RSpec.configure do |config|
   config.include(Matchers)
@@ -112,7 +117,8 @@ 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 :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?
@@ -126,6 +132,7 @@ RSpec.configure do |config|
   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?
@@ -146,7 +153,7 @@ RSpec.configure do |config|
   config.filter_run_excluding :aes_256_gcm_only => true unless aes_256_gcm?
   config.filter_run_excluding :broken => true
 
-  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 +164,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
   }
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/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/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/windows_script.rb b/spec/support/shared/functional/windows_script.rb
index 35b86dc..3499cc9 100644
--- a/spec/support/shared/functional/windows_script.rb
+++ b/spec/support/shared/functional/windows_script.rb
@@ -114,7 +114,7 @@ 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
diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb
index e6942c6..927ff2f 100644
--- a/spec/support/shared/integration/integration_helper.rb
+++ b/spec/support/shared/integration/integration_helper.rb
@@ -22,14 +22,19 @@ 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 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 +54,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/lib/chef/resource/deploy_revision.rb b/spec/support/shared/unit/knife_shared.rb
similarity index 50%
copy from lib/chef/resource/deploy_revision.rb
copy to spec/support/shared/unit/knife_shared.rb
index e144ce2..8c9010f 100644
--- a/lib/chef/resource/deploy_revision.rb
+++ b/spec/support/shared/unit/knife_shared.rb
@@ -1,6 +1,6 @@
 #
-# Author:: Daniel DeLeo (<dan at kallistec.com>)
-# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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");
@@ -16,30 +16,25 @@
 # limitations under the License.
 #
 
-class Chef
-  class Resource
 
-    # 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
+shared_examples_for "mandatory field missing" do
+  context "when field is nil" do
+    before do
+      knife.name_args = name_args
     end
 
-    class DeployBranch < Chef::Resource::DeployRevision
-
-      provides :deploy_branch
+    it "exits 1" do
+      expect { knife.run }.to raise_error(SystemExit)
+    end
 
-      def initialize(*args, &block)
-        super
-        @resource_name = :deploy_branch
-      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 must specify a #{fieldname}/
+    end
   end
 end
diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb
index 86f32c9..7de9698 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)
@@ -529,26 +529,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/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..64a6bcc 100644
--- a/spec/unit/application/client_spec.rb
+++ b/spec/unit/application/client_spec.rb
@@ -60,7 +60,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 +77,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 +165,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
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_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/client_spec.rb b/spec/unit/client_spec.rb
index fa83177..8146774 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,6 +361,7 @@ 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)
 
@@ -692,7 +374,10 @@ describe Chef::Client do
       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]).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
@@ -715,7 +400,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 +411,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 +460,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 +505,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
deleted file mode 100644
index 6ea6724..0000000
--- a/spec/unit/config_spec.rb
+++ /dev/null
@@ -1,544 +0,0 @@
-#
-# 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
-
-    context "when the url is a frozen string" do
-      before do
-        Chef::Config.chef_server_url = " https://junglist.gen.nz".freeze
-      end
-
-      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
-    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]])
-    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)
-    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..d295472 100644
--- a/spec/unit/cookbook/metadata_spec.rb
+++ b/spec/unit/cookbook/metadata_spec.rb
@@ -304,6 +304,21 @@ 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 "attribute groupings" do
diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb
index 471fc01..ee4e0be 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') }
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..2e1f3c3 100644
--- a/spec/unit/deprecation_spec.rb
+++ b/spec/unit/deprecation_spec.rb
@@ -95,4 +95,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/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..7e43b19
--- /dev/null
+++ b/spec/unit/event_dispatch/dispatcher_spec.rb
@@ -0,0 +1,61 @@
+#
+# 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")
+
+      expect(event_sink).to receive(:synchronized_cookbook).with("apache2")
+      dispatcher.synchronized_cookbook("apache2")
+
+      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
+
+  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..d018207
--- /dev/null
+++ b/spec/unit/formatters/doc_spec.rb
@@ -0,0 +1,46 @@
+#
+# 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
+
+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..5f95beb 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,122 @@ 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)
-    end
+  # Change to $stdout to print error messages for manual inspection
+  let(:stdout) { StringIO.new }
+
+  let(:outputter) { Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) }
 
-    it "finds the line number of the error from the stacktrace" do
-      expect(@inspector.culprit_line).to eq(14)
+  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 "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 +175,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 }
 
-    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")
+    let(:path_to_failed_file) { "/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 "prints a pretty message" do
-      @description.display(@outputter)
+      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 "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/json_compat_spec.rb b/spec/unit/json_compat_spec.rb
index 7482ba8..aaf0c48 100644
--- a/spec/unit/json_compat_spec.rb
+++ b/spec/unit/json_compat_spec.rb
@@ -67,7 +67,7 @@ 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
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_spec.rb b/spec/unit/knife/bootstrap_spec.rb
index f1ca510..0195e6d 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)
@@ -531,6 +531,7 @@ describe Chef::Knife::Bootstrap do
   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"
       allow(knife).to receive(:render_template).and_return("")
@@ -590,6 +591,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 +611,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/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb
index 7f9308b..219a1f2 100644
--- a/spec/unit/knife/core/subcommand_loader_spec.rb
+++ b/spec/unit/knife/core/subcommand_loader_spec.rb
@@ -22,14 +22,14 @@ 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) 
+    Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
   end
 
   it "builds a list of the core subcommand file require paths" do
@@ -106,6 +106,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 +145,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",
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/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/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..022256f 100644
--- a/spec/unit/knife_spec.rb
+++ b/spec/unit/knife_spec.rb
@@ -30,11 +30,20 @@ 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)
+  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)
@@ -130,7 +139,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 +261,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
@@ -374,6 +396,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..bcb64cb 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 "LWRP 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/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/template_spec.rb b/spec/unit/mixin/template_spec.rb
index f02bd34..6a867b5 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
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/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..032cb1a 100644
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -1106,7 +1106,7 @@ 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
+    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
diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb
index 1dbd07a..33d4c2c 100644
--- a/spec/unit/platform/query_helpers_spec.rb
+++ b/spec/unit/platform/query_helpers_spec.rb
@@ -20,7 +20,7 @@ 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)
+    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
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/policyfile_spec.rb b/spec/unit/policy_builder/policyfile_spec.rb
index e4f7388..5fa00d8 100644
--- a/spec/unit/policy_builder/policyfile_spec.rb
+++ b/spec/unit/policy_builder/policyfile_spec.rb
@@ -166,13 +166,17 @@ 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) }
 
@@ -389,8 +393,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 }
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..f6bb788 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"
@@ -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..38d6db8 100644
--- a/spec/unit/provider/directory_spec.rb
+++ b/spec/unit/provider/directory_spec.rb
@@ -16,173 +16,237 @@
 # 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
+
+        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
 
+    describe "when the directory does not exist" do
+      before do
+        FileUtils.rmdir tmp_dir
+      end
 
-  describe "scanning file security metadata on windows" do
-    before do
+      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
 
-    it "describes the directory's access rights" do
-      skip
-    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
-    end
+  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 "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 "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
 
-    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 "#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 "should not set the resource as updated" do
+        directory.run_action(:create)
+        expect(new_resource).not_to be_updated
       end
     end
 
-    context "when user/group are specified with user/group names" do
+    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
-  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 "when the parent directory does not exist" do
+      before do
+        new_resource.path "#{tmp_dir}/foobar"
+        FileUtils.rmdir tmp_dir
+      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 "raises an exception when recursive is false" do
+        new_resource.recursive false
+        expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+      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 "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 "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)
-  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
 
-  # 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
+      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
 
+  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 "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
+      it "sets the new resource as updated" do
+        directory.run_action(:delete)
+        expect(new_resource).to be_updated
+      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 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
+    describe "when the directory does not exist" do
+      before do
+        FileUtils.rmdir tmp_dir
+      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 "does not delete the directory" do
+        expect(Dir).not_to receive(:delete).with(new_resource.path)
+        directory.run_action(:delete)
+      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
+      it "sets the new resource as updated" do
+        directory.run_action(:delete)
+        expect(new_resource).not_to be_updated
+      end
+    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
+    describe "when the directory is not writable" do
+      before do
+        allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+      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
+      it "cannot delete it and raises an exception" do
+        expect {  directory.run_action(:delete) }.to raise_error(RuntimeError)
+      end
+    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..65c1c01 100644
--- a/spec/unit/provider/dsc_resource_spec.rb
+++ b/spec/unit/provider/dsc_resource_spec.rb
@@ -35,10 +35,10 @@ describe Chef::Provider::DscResource do
       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
 
@@ -56,7 +56,7 @@ describe Chef::Provider::DscResource do
         expect(provider).to receive(:meta_configuration).and_return(
                                                              meta_configuration)
         expect { provider.run_action(:run) }.to raise_error(
-          Chef::Exceptions::NoProviderAvailable, /Disabled/)
+          Chef::Exceptions::ProviderNotFound, /Disabled/)
       end
     end
 
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..1274203 100644
--- a/spec/unit/provider/execute_spec.rb
+++ b/spec/unit/provider/execute_spec.rb
@@ -39,7 +39,7 @@ describe Chef::Provider::Execute do
   let(:new_resource) { Chef::Resource::Execute.new("foo_resource", run_context) }
 
   before do
-    allow(Chef::Platform).to receive(:windows?) { false }
+    allow(ChefConfig).to receive(:windows?) { false }
     @original_log_level = Chef::Log.level
     Chef::Log.level = :info
     allow(STDOUT).to receive(:tty?).and_return(true)
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/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..4974cff 100644
--- a/spec/unit/provider/package/dpkg_spec.rb
+++ b/spec/unit/provider/package/dpkg_spec.rb
@@ -51,7 +51,7 @@ describe Chef::Provider::Package::Dpkg do
     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)
+        allow(@provider).to receive(:shell_out).with("dpkg-deb -W #{@new_resource.source}", timeout: 900).and_return(@status)
         @provider.load_current_resource
         expect(@provider.current_resource.package_name).to eq("wget")
         expect(@new_resource.version).to eq(version)
@@ -106,7 +106,7 @@ 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 wget", timeout: 900).and_return(status)
 
       @provider.load_current_resource
       expect(@provider.current_resource.version).to eq("1.11.4-1ubuntu1")
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..e0e45d0 100644
--- a/spec/unit/provider/package/rpm_spec.rb
+++ b/spec/unit/provider/package/rpm_spec.rb
@@ -23,183 +23,394 @@ 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 }
 
-      %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
+      let(:action) { :install }
+
+      context "when the source is a file system path" do
+
+        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
 
-      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..67ffb7b 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] }
@@ -542,7 +508,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 +517,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 +527,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 +538,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 +584,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 +593,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 +605,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 +644,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 +658,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_spec.rb b/spec/unit/provider/package/windows_spec.rb
index d402113..e5acc87 100644
--- a/spec/unit/provider/package/windows_spec.rb
+++ b/spec/unit/provider/package/windows_spec.rb
@@ -19,50 +19,129 @@
 require 'spec_helper'
 
 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(:new_resource) { Chef::Resource::WindowsPackage.new(resource_source) }
   let(:provider) { Chef::Provider::Package::Windows.new(new_resource, run_context) }
+  let(:cache_path) { 'c:\\cache\\' }
 
   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_source)
+      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
+
+      context "when remote_file_attributes are provided" do
+        let (:remote_file_attributes) { {:path => 'C:\\foobar.msi'} }
+        before(:each) do
+          new_resource.remote_file_attributes(remote_file_attributes)
+        end
+
+        it 'should override the attributes of the remote file resource used' do
+          expect(::File).to receive(:exists?).with(remote_file_attributes[:path])
+          provider.load_current_resource
+        end
+
+      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 "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
+      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
 
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
index 865dce2..e878b92 100644
--- a/spec/unit/provider/package/yum_spec.rb
+++ b/spec/unit/provider/package/yum_spec.rb
@@ -17,6 +17,7 @@
 #
 
 require 'spec_helper'
+require 'securerandom'
 
 describe Chef::Provider::Package::Yum do
   before(:each) do
@@ -122,6 +123,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(
@@ -1659,6 +1680,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 +1733,19 @@ 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
     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 +1762,24 @@ 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 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
@@ -2041,6 +2095,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
@@ -2076,5 +2160,31 @@ describe "Chef::Provider::Package::Yum - Multi" do
       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(
+        "yum -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..432d968 100644
--- a/spec/unit/provider/package_spec.rb
+++ b/spec/unit/provider/package_spec.rb
@@ -37,6 +37,12 @@ describe Chef::Provider::Package do
       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)
@@ -698,4 +704,38 @@ describe "Chef::Provider::Package - Multi" do
       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/powershell_spec.rb b/spec/unit/provider/powershell_spec.rb
index 60dbcf8..855c18a 100644
--- a/spec/unit/provider/powershell_spec.rb
+++ b/spec/unit/provider/powershell_spec.rb
@@ -19,20 +19,62 @@
 require 'spec_helper'
 describe Chef::Provider::PowershellScript, "action_run" do
 
-  before(:each) do
-    @node = Chef::Node.new
+  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
+  }
 
-    @node.default["kernel"] = Hash.new
-    @node.default["kernel"][:machine] = :x86_64.to_s
+  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)
+  }
 
-    @run_context = Chef::RunContext.new(@node, {}, @events)
-    @new_resource = Chef::Resource::PowershellScript.new('run some powershell code', @run_context)
+  context 'when setting interpreter flags' do
+    it "should set the -File flag as the last flag" do
+      expect(provider.flags.split(' ').pop).to eq("-File")
+    end
 
-    @provider = Chef::Provider::PowershellScript.new(@new_resource, @run_context)
-  end
+    let(:execution_policy_flag) do
+      execution_policy_index = 0
+      provider_flags = provider.flags.split(' ')
+      execution_policy_specified = false
 
-  it "should set the -File flag as the last flag" do
-    expect(@provider.flags.split(' ').pop).to eq("-File")
-  end
+      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 "should set 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 "should set the -ExecutionPolicy flag to '#{version_policy[1]}'" do
+          expect(execution_policy_flag.downcase).to eq(version_policy[1].downcase)
+        end
+      end
+    end
+  end
 end
diff --git a/spec/unit/provider/remote_directory_spec.rb b/spec/unit/provider/remote_directory_spec.rb
index 4434714..99e2fe2 100644
--- a/spec/unit/provider/remote_directory_spec.rb
+++ b/spec/unit/provider/remote_directory_spec.rb
@@ -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
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/service/aix_service_spec.rb b/spec/unit/provider/service/aix_service_spec.rb
index 7966611..a0c8bb3 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/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb
index 5ea037d..32d0812 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
diff --git a/spec/unit/provider/user_spec.rb b/spec/unit/provider/user_spec.rb
index 3811686..2345ce1 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
diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb
index bdf6d06..88df4a2 100644
--- a/spec/unit/provider_resolver_spec.rb
+++ b/spec/unit/provider_resolver_spec.rb
@@ -19,35 +19,116 @@
 require 'spec_helper'
 require 'chef/mixin/convert_to_class_name'
 require 'chef/provider_resolver'
+require 'chef/platform/service_helpers'
 
 include Chef::Mixin::ConvertToClassName
 
+# Open up Provider so we can write things down easier in here
+#module Chef::Provider
+
 describe Chef::ProviderResolver do
 
+  let(:resource_name) { :service }
+  let(:provider) { nil }
+  let(:action) { :start }
+
   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.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
+  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
 
-  let(:action) { :start }
+  let(:resource) do
+    resource_class = Chef::ResourceResolver.resolve(resource_name, node: node)
+    if resource_class
+      resource = resource_class.new('test', run_context)
+      resource.provider = provider if provider
+    end
+    resource
+  end
+
+  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
 
-  let(:resolved_provider) { provider_resolver.resolve }
+  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 }
 
-  let(:provider) { nil }
+      define_singleton_method(:os) { os }
+      define_singleton_method(:platform) { platform }
+      define_singleton_method(:platform_family) { platform_family }
+      define_singleton_method(:platform_version) { platform_version }
 
-  let(:resource_name) { :service }
+      instance_eval(&block)
+    end
+  end
 
-  let(:resource) { double(Chef::Resource, provider: provider, resource_name: resource_name) }
+  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
 
-  before do
-    allow(resource).to receive(:is_a?).with(Chef::Resource).and_return(true)
+        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
 
   describe "resolving service resource" do
@@ -64,7 +145,6 @@ describe Chef::ProviderResolver do
     end
 
     before do
-      expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup)
       allow(resource).to receive(:service_name).and_return("ntp")
     end
 
@@ -301,389 +381,479 @@ describe Chef::ProviderResolver do
       end
     end
 
-    describe "on Ubuntu 14.10" do
-      let(:os) { "linux" }
-      let(:platform) { "ubuntu" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "14.04" }
-
+    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
 
-    describe "on Ubuntu 14.04" do
-      let(:os) { "linux" }
-      let(:platform) { "ubuntu" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "14.04" }
-
+    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
 
-    describe "on Ubuntu 10.04" do
-      let(:os) { "linux" }
-      let(:platform) { "ubuntu" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "10.04" }
-
+    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???)
-    describe "on Debian 4.0" do
-      let(:os) { "linux" }
-      let(:platform) { "debian" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "4.0" }
-
+    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
-    describe "on Debian 7.0" do
-      let(:os) { "linux" }
-      let(:platform) { "debian" }
-      let(:platform_family) { "debian" }
-      let(:platform_version) { "7.0" }
-
+    on_platform "debian", platform_version: "7.0", os: "linux" do
       it_behaves_like "a debian platform using the insserv provider"
     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" }
-
-        it "returns a Solaris provider" do
-          stub_service_providers
-          stub_service_configs
-          expect(resolved_provider).to eql(Chef::Provider::Service::Solaris)
-        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
+        expect(resolved_provider).to eql(Chef::Provider::Service::Solaris)
+      end
 
-        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)
-          expect(resolved_provider).to eql(Chef::Provider::Service::Solaris)
-        end
+      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)
+        expect(resolved_provider).to eql(Chef::Provider::Service::Solaris)
       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" }
-
-        it "returns a Windows provider" do
-          stub_service_providers
-          stub_service_configs
-          expect(resolved_provider).to eql(Chef::Provider::Service::Windows)
-        end
+    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
+        expect(resolved_provider).to eql(Chef::Provider::Service::Windows)
+      end
 
-        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)
-          expect(resolved_provider).to eql(Chef::Provider::Service::Windows)
-        end
+      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)
+        expect(resolved_provider).to eql(Chef::Provider::Service::Windows)
       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" }
-
-        it "returns a Macosx provider" do
-          stub_service_providers
-          stub_service_configs
-          expect(resolved_provider).to eql(Chef::Provider::Service::Macosx)
-        end
+    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
+        expect(resolved_provider).to eql(Chef::Provider::Service::Macosx)
+      end
 
-        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)
-          expect(resolved_provider).to eql(Chef::Provider::Service::Macosx)
-        end
+      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)
+        expect(resolved_provider).to eql(Chef::Provider::Service::Macosx)
       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" }
-
-        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)
-          expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
-        end
+    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)
+        expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
+      end
 
-        it "returns a Freebsd provider if it finds the /etc/rc.d initscript" do
-          stub_service_providers
-          stub_service_configs(:etc_rcd)
-          expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
-        end
+      it "returns a Freebsd provider if it finds the /etc/rc.d initscript" do
+        stub_service_providers
+        stub_service_configs(:etc_rcd)
+        expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
+      end
 
-        it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
-          # should only care about :usr_local_etc_rcd stub in the service configs
-          stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
-          stub_service_configs(:usr_local_etc_rcd, :initd, :upstart, :xinetd, :systemd)
-          expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
-        end
+      it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
+        # should only care about :usr_local_etc_rcd stub in the service configs
+        stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
+        stub_service_configs(:usr_local_etc_rcd, :initd, :upstart, :xinetd, :systemd)
+        expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
+      end
 
-        it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
-          # should only care about :etc_rcd stub in the service configs
-          stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
-          stub_service_configs(:etc_rcd, :initd, :upstart, :xinetd, :systemd)
-          expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
-        end
+      it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
+        # should only care about :etc_rcd stub in the service configs
+        stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
+        stub_service_configs(:etc_rcd, :initd, :upstart, :xinetd, :systemd)
+        expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
+      end
 
-        it "foo" do
-          stub_service_providers
-          stub_service_configs
-          expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
-        end
+      it "foo" do
+        stub_service_providers
+        stub_service_configs
+        expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
       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
+  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 ],
+          },
+        },
+      },
 
-    %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,
+
+    "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" => {
-      :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,
+      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" => {
-      :cron => Chef::Provider::Cron::Aix,
-    },
-    "netbsd"=> {
-      :group => Chef::Provider::Group::Groupmod,
+      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" => {
+          },
+        },
+      },
     },
-    "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" }
+    "hpux" => {
+      "hpux" => {
+        "hpux" => {
+          "3.1.4" => {
+            group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ]
+          }
+        }
+      }
+    },
 
-        do_platform(provider_mapping[platform])
-      end
-    end
+    "netbsd" => {
+      "netbsd" => {
+        "netbsd" => {
+          "3.1.4" => {
+            group: [ Chef::Resource::Group, Chef::Provider::Group::Groupmod ],
+          },
+        },
+      },
+    },
 
-    describe "on AIX" do
-      let(:os) { "aix" }
-      let(:platform) { "aix" }
-      let(:platform_family) { "aix" }
-      let(:platform_version) { "6.2" }
+    "openbsd" => {
+      group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
+      package: [ Chef::Resource::OpenbsdPackage, Chef::Provider::Package::Openbsd ],
+
+      "openbsd" => {
+        "openbsd" => {
+          "3.1.4" => {
+          },
+        },
+      },
+    },
 
-      do_platform(provider_mapping['aix'])
-    end
+    "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" => {
+          },
+        },
+      },
 
-    %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
+    "solaris" => {
+      "solaris" => {
+        "solaris" => {
+          "3.1.4" => {
+          },
+        },
+      },
+    },
 
-  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,
+    "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)
+  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
-        end
+        create_provider_tests(value, next_test, expected, next_filter)
       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
-        end
+    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
diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb
index 5a21b09..d7a34bc 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
@@ -176,6 +187,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..511e7e9 100644
--- a/spec/unit/recipe_spec.rb
+++ b/spec/unit/recipe_spec.rb
@@ -83,7 +83,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 +121,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 +132,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 +143,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 +153,24 @@ 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
+            expect(Chef::Log).to receive(:warn).with("You declared a new resource TottenhamHotspur for resource football, but it comes alphabetically after Sounders and has the same filters ({:platform=>\"nbc_sports\"}), so it will not be used. Use override: true if you want to use it for football.")
+
+            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
+            expect(Chef::Log).to receive(:warn).with("You are overriding football2 on {:platform=>\"nbc_sports\"} with Sounders: used to be TottenhamHotspur. Use override: true if this is what you intended.")
+
+            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
@@ -409,7 +410,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 +578,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
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/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/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..06769d8 100644
--- a/spec/unit/resource/dsc_resource_spec.rb
+++ b/spec/unit/resource/dsc_resource_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::DscResource do
     }
 
     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
diff --git a/spec/unit/resource/dsc_script_spec.rb b/spec/unit/resource/dsc_script_spec.rb
index 71103ea..4361b35 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
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_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/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_spec.rb
index c263172..2505c4a 100644
--- a/spec/unit/resource/powershell_spec.rb
+++ b/spec/unit/resource/powershell_spec.rb
@@ -25,6 +25,7 @@ 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)
 
diff --git a/spec/unit/resource/registry_key_spec.rb b/spec/unit/resource/registry_key_spec.rb
index e2a864d..2e2811d 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|
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/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/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_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_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..e0aee58 100644
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -21,10 +21,6 @@
 
 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')
@@ -35,6 +31,18 @@ describe Chef::Resource do
     @resource = Chef::Resource.new("funk", @run_context)
   end
 
+  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
 
     it "adds an entry to a list of subclasses" do
@@ -324,6 +332,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")
@@ -343,7 +431,7 @@ describe Chef::Resource 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) { @resource }
     end
   end
@@ -447,8 +535,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
@@ -709,57 +810,73 @@ 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 = Chef::Node.new
+        @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
 
@@ -860,4 +977,90 @@ describe Chef::Resource do
     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..3b04981 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"=>"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"=>"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_spec.rb b/spec/unit/run_context_spec.rb
index d656111..e20ba63 100644
--- a/spec/unit/run_context_spec.rb
+++ b/spec/unit/run_context_spec.rb
@@ -53,6 +53,37 @@ 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",
+        },
+        "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
+
   describe "loading cookbooks for a run list" do
     before do
 
@@ -159,4 +190,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/versioned_recipe_list_spec.rb b/spec/unit/run_list/versioned_recipe_list_spec.rb
index 209ac37..9c3ecaa 100644
--- a/spec/unit/run_list/versioned_recipe_list_spec.rb
+++ b/spec/unit/run_list/versioned_recipe_list_spec.rb
@@ -26,98 +26,165 @@ 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
+
 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/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/tasks/external_tests.rb b/tasks/external_tests.rb
new file mode 100644
index 0000000..2ff991d
--- /dev/null
+++ b/tasks/external_tests.rb
@@ -0,0 +1,29 @@
+task :chef_sugar_spec do
+  gem_path = Bundler.environment.specs['chef-sugar'].first.full_gem_path
+  system("cd #{gem_path} && rake")
+end
+
+task :foodcritic_spec do
+  gem_path = Bundler.environment.specs['foodcritic'].first.full_gem_path
+  system("cd #{gem_path} && rake test")
+end
+
+task :chefspec_spec do
+  gem_path = Bundler.environment.specs['chefspec'].first.full_gem_path
+  system("cd #{gem_path} && rake")
+end
+
+task :chef_rewind_spec do
+  gem_path = Bundler.environment.specs['chef-rewind'].first.full_gem_path
+  system("cd #{gem_path} && rake spec")
+end
+
+task :poise_spec do
+  gem_path = Bundler.environment.specs['poise'].first.full_gem_path
+  system("cd #{gem_path} && rake spec")
+end
+
+task :halite_spec do
+  gem_path = Bundler.environment.specs['halite'].first.full_gem_path
+  system("cd #{gem_path} && rake spec")
+end
diff --git a/tasks/maintainers.rb b/tasks/maintainers.rb
new file mode 100644
index 0000000..5a2c8d9
--- /dev/null
+++ b/tasks/maintainers.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 'rake'
+
+SOURCE = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.toml")
+TARGET = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.md")
+
+begin
+  require 'tomlrb'
+  task :default => :generate
+
+  namespace :maintainers do
+    desc "Generate MarkDown version of MAINTAINERS file"
+    task :generate do
+      maintainers = Tomlrb.load_file SOURCE
+      out = "<!-- This is a generated file. Please do not edit directly -->\n\n"
+      out << "# " + maintainers["Preamble"]["title"] + "\n\n"
+      out <<  maintainers["Preamble"]["text"] + "\n"
+      out << "# " + maintainers["Org"]["Lead"]["title"] + "\n\n"
+      out << person(maintainers["people"], maintainers["Org"]["Lead"]["person"]) + "\n\n"
+      out << components(maintainers["people"], maintainers["Org"]["Components"])
+      File.open(TARGET, "w") { |fn|
+        fn.write out
+      }
+    end
+  end
+
+  def components(list, cmp)
+    out = "## " + cmp.delete("title") + "\n\n"
+    out << cmp.delete("text") + "\n" if cmp.has_key?("text")
+    if cmp.has_key?("lieutenant")
+      out << "### Lieutenant\n\n"
+      out << person(list, cmp.delete("lieutenant")) + "\n\n"
+    end
+    out << maintainers(list, cmp.delete("maintainers")) + "\n" if cmp.has_key?("maintainers")
+    cmp.delete("paths")
+    cmp.each {|k,v| out << components(list, v) }
+    out
+  end
+
+  def maintainers(list, people)
+    o = "### Maintainers\n\n"
+    people.each do |p|
+      o << person(list, p) + "\n"
+    end
+    o
+  end
+
+  def person(list, person)
+    "* [#{list[person]["Name"]}](https://github.com/#{list[person]["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..6e802d3 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 --local")
+        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